]> git.decadent.org.uk Git - dak.git/blob - dak/bts_categorize.py
Merge remote-tracking branch 'drkranz/fixes' into merge
[dak.git] / dak / bts_categorize.py
1 #!/usr/bin/python
2
3 """
4 bts -- manage bugs filed against ftp.debian.org
5
6 @contact: Debian FTP Master <ftpmaster@debian.org>
7 @copyright: 2009 Mike O'Connor <stew@vireo.org>
8 @copyright: 2010 Alexander Reichle-Schmehl <tolimar@debian.org>
9 @license: GNU General Public License version 2 or later
10 """
11
12 #  This program is free software; you can redistribute it and/or modify it
13 #  under the terms of the GNU General Public License as published by the
14 #  Free Software Foundation; either version 2, or (at your option) any
15 #  later version.
16 #
17 #  This program is distributed in the hope that it will be useful,
18 #  but WITHOUT ANY WARRANTY; without even the implied warranty of
19 #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20 #  GNU General Public License for more details.
21 #
22 #  You should have received a copy of the GNU General Public License
23 #  along with this program; if not, write to the Free Software
24 #  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
25 #  USA.
26
27 ################################################################################
28 ################################################################################
29
30 import sys
31 import re
32 import logging
33 log = logging.getLogger()
34
35 import apt_pkg
36 from daklib import utils
37 import debianbts as bts
38
39 def usage():
40     print """
41 SYNOPSIS
42     dak bts-categorize [options]
43
44 OPTIONS
45     -s
46     --simulate
47         Don't send email, instead output the lines that would be sent to
48         control@b.d.o.
49
50     -v
51     --verbose
52         Print more informational log messages
53
54     -q
55     --quiet
56         Suppress informational messages
57
58     -h
59     --help
60         Print this documentation.
61 """
62
63 arguments = [('s','simulate','BtsCategorize::Options::Simulate'),
64              ('v', 'verbose', 'BtsCategorize::Options::Verbose'),
65              ('q', 'quiet', 'BtsCategorize::Options::Quiet'),
66              ('h', 'help', 'BtsCategorize::Options::Help')]
67
68 class BugClassifier(object):
69     """
70     classify bugs using usertags based on the bug subject lines
71
72     >>> BugClassifier.rm_re.match( "RM: asdf" ) != None
73     True
74     >>> BugClassifier.rm_re.match( "[dak] Packages.diff/Index broken" ) != None
75     False
76     >>> BugClassifier.dak_re.match( "[dak] Packages.diff/Index broken" ) != None
77     True
78     """
79     rm_re = re.compile( "^RM" )
80     dak_re = re.compile( "^\[dak\]" )
81     arch_re = re.compile( "^\[Architectures\]" )
82     override_re = re.compile( "^override" )
83
84     classifiers = { rm_re: 'remove',
85                     dak_re: 'dak',
86                     arch_re: 'archs',
87                     override_re: 'override'}
88
89     def unclassified_bugs(self):
90         """
91         Returns a list of open bugs which have not yet been classified
92         by one of our usertags.
93         """
94
95         tagged_bugs = bts.get_usertag('ftp.debian.org@packages.debian.org')
96         tagged_bugs_ftp = []
97         for tags in tagged_bugs.keys():
98                 tagged_bugs_ftp += tagged_bugs[tags]
99
100         return [ bug for bug in bts.get_status( bts.get_bugs("package", "ftp.debian.org" ) ) \
101                      if bug.pending=='pending' and not bug.bug_num in tagged_bugs_ftp ]
102
103
104     def classify_bug(self, bug):
105         """
106         if any of our classifiers match, return a newline terminated
107         command to set an appropriate usertag, otherwise return an
108         empty string
109         """
110         retval = ""
111
112         for classifier in self.classifiers.keys():
113             if classifier.match(bug.subject):
114                 retval = "usertag %s %s\n" % (bug.bug_num,
115                                             self.classifiers[classifier])
116                 break
117
118         if retval:
119             log.info(retval)
120         else:
121             log.debug("Unmatched: [%s] %s" % (bug.bug_num, bug.subject))
122
123         return retval
124
125     def email_text(self):
126         controls = ""
127
128         bc = BugClassifier()
129         try:
130             for bug in bc.unclassified_bugs():
131                 controls += bc.classify_bug(bug)
132
133             return controls
134         except:
135             log.error("couldn't retrieve bugs from soap interface: %s" % sys.exc_info()[0])
136             return None
137
138 def send_email(commands, simulate=False):
139     global Cnf
140
141     Subst = {'__COMMANDS__' : commands,
142              "__DAK_ADDRESS__": Cnf["Dinstall::MyAdminAddress"]}
143
144     bts_mail_message = utils.TemplateSubst(
145         Subst,Cnf["Dir::Templates"]+"/bts-categorize")
146
147     if simulate:
148         print bts_mail_message
149     else:
150         utils.send_mail( bts_mail_message )
151
152 def main():
153     """
154     for now, we just dump a list of commands that could be sent for
155     control@b.d.o
156     """
157     global Cnf
158     Cnf = utils.get_conf()
159
160     for arg in arguments:
161         opt = "BtsCategorize::Options::%s" % arg[1]
162         if not Cnf.has_key(opt):
163             Cnf[opt] = ""
164
165     packages = apt_pkg.parse_commandline(Cnf, arguments, sys.argv)
166     Options = Cnf.subtree('BtsCategorize::Options')
167
168     if Options["Help"]:
169         usage()
170         sys.exit( 0 )
171
172     if Options["Quiet"]:
173         level=logging.ERROR
174
175     elif Options["Verbose"]:
176         level=logging.DEBUG
177
178     else:
179         level=logging.INFO
180
181     logging.basicConfig( level=level,
182                          format='%(asctime)s %(levelname)s %(message)s',
183                          stream = sys.stderr )
184
185     body = BugClassifier().email_text()
186
187     if body:
188         send_email(body, Options["Simulate"])
189
190     else:
191         log.info( "nothing to do" )
192
193
194 if __name__ == '__main__':
195 #    import doctest
196 #    doctest.testmod()
197     main()