Source code for sr.tools.trac

from collections import Counter
import re

from six.moves.xmlrpc_client import ServerProxy

from sr.tools.config import Config


[docs]class WrongServer(Exception): """The RPC server specified isn't a trac instance.""" pass
[docs]class TracProxy(ServerProxy): """ An XML-RPC proxy for SR Trac. :param user: The username. By default this is looked up in the config or the user is prompted for it. :param password: The password to use. If left as its default value of None, it may be looked up in the keyring, or the user may be prompted for it. :param server: The server hostname. Defaults to that found in the config. :param port: The HTTPS port of the server. Defaults to that found in the config. :param anon: Whether to use trac anonymously. """ def __init__(self, user=None, password=None, server=None, port=None, anon=False): """Initialise an SR trac object.""" config = Config() if server is None: server = config["server"] if port is None: port = config["https_port"] self.server = server self.port = port rpc_settings = {"server": server, "port": port} if anon: rpc_url = "https://{server}:{port}/trac/rpc" else: rpc_url = "https://{user}:{password}@{server}:{port}/trac/login" \ "/rpc" user = config.get_user(user) rpc_settings["user"] = user rpc_settings["password"] = config.get_password(password, user=user) rpc_url = rpc_url.format(**rpc_settings) ServerProxy.__init__(self, rpc_url) if 'ticket.create' not in self.system.listMethods(): raise WrongServer()
[docs]class Ticket(object): """ A ticket that may have dependencies. :param int num: The ticket number. :param proxy: The XMLRPC proxy object. """ def __init__(self, num, proxy): """Create a new ticket object.""" self.proxy = proxy self.num = num self.refresh()
[docs] def refresh(self): """Refresh with data from trac.""" _, _, _, ticket = self.proxy.ticket.get(self.num) desc = self.desc = ticket["description"] self.status = ticket["status"] self.resolution = ticket["resolution"] self.summary = ticket["summary"] self.changetime = ticket["changetime"] self.component = ticket["component"] self.keywords = ticket["keywords"] self.milestone = ticket["milestone"] self.owner = ticket["owner"] self.cc = ticket["cc"] self.priority = ticket["priority"] self.reporter = ticket["reporter"] self.time = ticket["time"] self.type = ticket["type"] self.version = ticket["version"] reg = self._construct_regex() self.deps = [] r = reg.match(desc) if r is None: # ticket has no dependencies self.prelude = desc self.deptitle = "" self.depspace = "" self.deplist = "" self.postscript = "" self.deplist_ends_in_newline = False self.list_prefix = "" return self.prelude = r.group("prelude") self.deptitle = r.group("deptitle") self.depspace = r.group("depspace") self.deplist = r.group("deplist") self.postscript = r.group("postscript") spacings = Counter() regex = r"^(\s*\*\s*)#([0-9]+)\s*(.*)$" opts = re.MULTILINE | re.IGNORECASE for asterisk, ticket_num, desc in re.findall(regex, self.deplist, opts): spacings.update([asterisk]) self.deps.append(int(ticket_num)) self.list_prefix = spacings.most_common(1)[0][0] self.deplist_ends_in_newline = (self.deplist[-1] == "\n")
@property def url(self): server, port = self.proxy.server, self.proxy.port if port == 443: base = 'https://{server}/trac/ticket/{num}' else: base = 'https://{server}:{port}/trac/ticket/{num}' return base.format(server=server, port=port, num=self.num)
[docs] def cleanup(self, dry_run=False, msg="Synchronise dependency summaries with dependencies " "(automated edit)"): """ Clean-up the ticket's description. :param bool dry_run: Whether or not to actually commit the changes. :param str msg: The message to be shown in Trac. :returns: Whether or not a change has occurred. :rtype: bool """ # Rebuild the deplist: if len(self.deps) != 0 and self.deptitle == "": self.deptitle = "\n\nDependencies:\n" self.list_prefix = " * " d = self.prelude + self.deptitle + self.depspace for i, ticket_num in enumerate(self.deps): _, _, _, dep = self.proxy.ticket.get(ticket_num) d += "{prefix}#{num} {summary}".format(prefix=self.list_prefix, num=ticket_num, summary=dep["summary"]) if i != len(self.deps) - 1 or self.deplist_ends_in_newline: d += "\n" d += self.postscript if d == self.desc: # description has not changed return False if not dry_run: self.proxy.ticket.update(self.num, msg, {"description": d}) self.refresh() return True
def _construct_regex(self): # Construct a regexp for splitting dependencies from the prelude # Prelude section: reg = r"(?P<prelude>.*)" # Dependencies line reg += r"(?P<deptitle>^Dependencies:?$)" # Possible newlines between dependencies line and dependency list reg += r"(?P<depspace>\s*\n)?" # Dependency list: reg += r"(?P<deplist>(^\s*\*[^\n]+($\n|\Z))*)" # Postscript after the dependency list: reg += r"(?P<postscript>.*)" return re.compile(reg, re.MULTILINE | re.IGNORECASE | re.DOTALL) def __str__(self): return "{0}: {1}".format(self.num, self.summary)