Add simple test for trac backend.
[jelmer/bts-link.git] / remote / trac.py
1 # vim:set encoding=utf-8:
2 ###############################################################################
3 # Copyright:
4 #   © 2006 Sanghyeon Seo   <sanxiyn@gmail.com>
5 #   © 2006 Pierre Habouzit <madcoder@debian.org>
6 #
7 # Redistribution and use in source and binary forms, with or without
8 # modification, are permitted provided that the following conditions
9 # are met:
10 # 1. Redistributions of source code must retain the above copyright
11 #    notice, this list of conditions and the following disclaimer.
12 # 2. Redistributions in binary form must reproduce the above copyright
13 #    notice, this list of conditions and the following disclaimer in the
14 #    documentation and/or other materials provided with the distribution.
15 # 3. The names of its contributors may not be used to endorse or promote
16 #    products derived from this software without specific prior written
17 #    permission.
18
19 # THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR
20 # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
21 # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO
22 # EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
24 # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
25 # OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
26 # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
27 # OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
28 # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 ###############################################################################
30
31 # @see http://projects.edgewall.com/trac/wiki/TracTickets
32
33 import re, urllib
34
35 from BeautifulSoup import BeautifulSoup
36 from __init__ import *
37
38 any = re.compile(r'.*')
39
40
41 class TracTicket:
42     def __init__(self, page):
43         lines = page.split('\n');
44
45         if len(lines) == 2:
46             keys = lines[0].split('\t')
47             vals = lines[1].split('\t')
48             dct  = dict((keys[i], vals[i]) for i in range(0, len(keys)))
49
50             self.status     = dct['status']
51             self.resolution = dct['resolution']
52             self.id         = int(dct['id'])
53             self.reporter   = dct['reporter']
54             self.owner      = dct['owner']
55             self.summary    = dct['summary']
56             self.milestone  = dct['milestone']
57             self.version    = dct['version']
58             self.priority   = dct['priority']
59             self.description = dct['description'].replace("\\r\\n", "\r\n")
60         else:
61             soup = BeautifulSoup(page)
62
63             status = soup.first(any, 'status')
64             if status:
65                 self.status, self.resolution = self._process_status(status.strong.string)
66             else:
67                 try:
68                     # Trac 0.8.x
69                     self.status = soup.first(any, dict(headers='h_status')).string
70                     resolution  = soup.first(any, dict(headers='h_resolution')).string
71                     self.resolution = (resolution != '&nbsp;' and resolution) or None
72                 except:
73                     failwith(uri, "Probably error page")
74
75     def _process_status(self, text):
76         words = text.split()
77         assert len(words) in (1, 2)
78         if len(words) == 1:
79             return text, None
80         else:
81             return [x.strip('()') for x in words]
82
83
84 class TracData:
85     def __init__(self, uri, id):
86         self.id = id or failwith(uri, "Trac: no id")
87
88         page = wget(uri + '?format=tab')
89
90         ticket = TracTicket(page)
91
92         self.status = ticket.status
93         self.resolution = ticket.resolution
94
95         if not ticket.status:
96             failwith(uri, "Trac: no status")
97
98         if ticket.resolution == "duplicate":
99             raise DupeExn(uri)
100
101
102 class RemoteTrac(RemoteBts):
103     def __init__(self, cnf):
104         bugre  =  r"^%(uri)s/ticket/([0-9]+)$"
105         urifmt = "%(uri)s/ticket/%(id)s"
106         RemoteBts.__init__(self, cnf, bugre, urifmt, TracData)
107
108     def isClosing(self, status, resolution):
109         return status == 'closed' and resolution != 'wontfix'
110
111     def isWontfix(self, status, resolution):
112         return resolution == 'wontfix'
113
114 RemoteBts.register('trac', RemoteTrac)