bugzilla: added busybox
[bts-link/bts-link.git] / remote / roundup.py
1 # vim:set encoding=utf-8:
2 ###############################################################################
3 # Copyright:
4 #   © 2009 Sandro Tosi <morph@debian.org>
5 #
6 # License:
7 #   Public Domain
8 ###############################################################################
10 # @see 
12 # TODO: handle duplicate, usually resolution=duplicate
13 # TODO: enable XMLRPC interface, but upstream needs version >= 1.4.8
15 # *** Doc ***
16 #
17 # bugs url: http://server/path/issueNNNN
18 # base url: http://server/path
19 #
20 # We are lucky Roundup allows 2 native querying methods:
21 #
22 #  * CSV export (the one currently used)
23 #  * XML RPC
24 #
25 # Roundup allows for a lot of customization, in particular for status
26 # and resolution values. In order to get those values, surf to
27 #
28 # status: <base url>/status
29 # resolution: <base url>/resolution
30 #
31 # the same thing done by getStatus() and getResolution() method below.
32 #
33 # Anyhow, we need to visit those pages because we have to populate
34 # 'closing' and 'wontfix' config values.
35 #
36 # Examples urls:
37 #
38 #   * issue url     : http://bugs.python.org/issue764437
39 #   * base uri      : http://bugs.python.org
40 #   * status url    : http://bugs.python.org/status
41 #   * resolution url: http://bugs.python.org/resolution
42 #
43 # How to write configuration entry:
44 #
45 #  * uri must NOT have tailing /
46 #  * write the 'closing' and 'wontfix' item following the above
47 #    suggestions
49 import urllib, urlparse, cgi, re
50 from __init__ import *
52 class RoundupData:
53     def __init__(self, uri, id):
54         # to uri is the real 'uri' with '/issueNNNNN' added, we need "real uri" 
55         uri_re = re.compile(r'(.*)/issue(.*)')
56         uri = uri_re.match(uri).group(1)
58         self.id = id or failwith(uri, "Roundup: no id")
60         self.status = self.getStatus(uri, id) or failwith(uri, "Roundup", exn=NoStatusExn)
61         self.resolution = self.getResolution(uri, id)
63         # NOT SUPPORTED YET
64         #if self.status == 'Duplicate':
65         #    raise DupeExn(uri)
67     def getStatus(self, uri, id):
68         """get the issue status in two steps: get the numerical status from the issue page, then match that id against the <id, status name> list in $uri/status page"""
69         # we now obtain the issue status, as a numerical value
70         status_tpl = [('@action', 'export_csv'), ('@filter', 'id'), ('id', id), ('@columns', 'status')]
71         status_url = '/%s?%s' % ('issue', urllib.urlencode(status_tpl))
72         status_content = urllib.urlopen(uri + status_url).read().split('\r\n')
73         # then retrive <id, status name> list from $uri/status
74         statuslist_tpl = [('@action', 'export_csv'), ('@columns', 'id,name')]
75         statuslist_url = '/%s?%s' % ('status', urllib.urlencode(statuslist_tpl))
76         statuslist_map = urllib.urlopen(uri + statuslist_url).read().split('\r\n')
77         statuslist_dict = {}
78         # let's make a dict out of this list: keys are numerical id, values are status names
79         # we strip first ('id,name') and last ('\n') values from the map
80         for statusitem in statuslist_map[1:len(statuslist_map)-1]:
81             s = statusitem.split(',')
82             statuslist_dict[s[0]] = s[1]
83         # if it has 3 items, we have data (we hope)
84         if len(status_content) == 3:
85             return statuslist_dict[status_content[1]]
86         else:
87             return None
88         
90     def getResolution(self, uri, id):
91         """get the issue resolution value in two steps: get the numerical resolution from the issue page, then match that id against the <id, resolution name> list in $uri/resolution page"""
92         # we now obtain the issue resolution, as a numerical value
93         resolv_tpl = [('@action', 'export_csv'), ('@filter', 'id'), ('id', id), ('@columns', 'resolution')]
94         resolv_url = '/%s?%s' % ('issue', urllib.urlencode(resolv_tpl))
95         resolv_content = urllib.urlopen(uri + resolv_url).read().split('\r\n')
96         # then retrive <id, status name> list from $uri/resolution - IF IT EXISTS!!
97         resolvlist_tpl = [('@action', 'export_csv'), ('@columns', 'id,name')]
98         resolvlist_url = '/%s?%s' % ('resolution', urllib.urlencode(resolvlist_tpl))
99         resolvlist_map = urllib.urlopen(uri + resolvlist_url).read().split('\r\n')
100         resolvlist_dict = {}
101         # let's make a dict out of this list: keys are numerical id, values are resolution names
102         # we strip first ('id,name') and last ('\n') values from the map
103         for resolvitem in resolvlist_map[1:len(resolvlist_map)-1]:
104             r = resolvitem.split(',')
105             resolvlist_dict[r[0]] = r[1]
106         # if it has 3 items, we have data (we hope)
107         if len(resolv_content) == 3 and resolv_content[1] != 'None':
108             return resolvlist_dict[resolv_content[1]]
109         else:
110             return None
112         pass
114 class RemoteRoundup(RemoteBts):
115     def __init__(self, cnf):
116         # save to an object attribute
117         self.cnf = cnf
119         bugre  = r"^%(uri)s/issue([0-9]+)$"
120         urifmt = "%(uri)s/issue%(id)s"
121         RemoteBts.__init__(self, cnf, bugre, urifmt, RoundupData)
123     def _getClosingStatus(self):
124         """get the config values for closing state"""
125         if 'closing' in self.cnf:
126             return self.cnf['closing']
127         else:
128             return [None]
130     def _getWontfixStatus(self):
131         """get the config value for wontfix state"""
132         if 'wontfix' in self.cnf:
133             return self.cnf['wontfix']
134         else:
135             return [None]
137     def isClosing(self, status, resolution):
138         return status in self._getClosingStatus()
140     def isWontfix(self, status, resolution):
141         return (status in [self._getWontfixStatus()]) \
142             or (resolution in [self._getWontfixStatus()])
145 RemoteBts.register('roundup', RemoteRoundup)