Subversion Repositories Scribus

Rev

Rev 12502 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
12491 malex 1
#!/usr/bin/env python
2
"""This script allows command line bug report submission to a Mantis
3
bug tracking system (http://www.mantisbt.org/)."""
4
###
5
# Copyright (c) 2008, Oleksandr Moskalenko
6
# All rights reserved.
7
#
8
#    This program is free software: you can redistribute it and/or modify
9
#    it under the terms of the GNU General Public License as published by
10
#    the Free Software Foundation, either version 3 of the License, or
11
#    (at your option) any later version.
12
#
13
#    This program is distributed in the hope that it will be useful,
14
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
15
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16
#    GNU General Public License for more details.
17
#
18
#    You should have received a copy of the GNU General Public License
19
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
20
###
21
 
12494 malex 22
import re, sys, logging
12491 malex 23
from mechanize import Browser
12494 malex 24
from BeautifulSoup import BeautifulSoup
12491 malex 25
 
26
##############################################################################
12494 malex 27
# USER CONFIGURATION - edit for your site if necessary.                      #
12491 malex 28
##############################################################################
12494 malex 29
basetitle = "^Mantis Issue Tracker"
12502 malex 30
username = None
31
password = None
32
baseurl = None
12491 malex 33
##############################################################################
34
 
35
class Mantisctl:
12494 malex 36
    """This class handles command line Mantis Bug Tracking System bug report
37
    submission. a mechanize.Browser() instance is initialized and used to
38
    control the site navigation and bug report submission. dorun is
39
    the only public method you really need to run to submit a bug report."""
12491 malex 40
    def __init__(self):
41
        self.br = Browser()
42
        self.br.set_handle_redirect(True)
43
        self.br.set_handle_referer(True)
12502 malex 44
        self.br.set_handle_refresh(True, max_time=10.0, honor_time=True)
12495 malex 45
#        self.br.set_handle_refresh(True, max_time=10.0)
46
#        self.br.set_handle_refresh(True, honor_time=False)
12494 malex 47
        (self.opts, self.args) = self.parseopts()
12491 malex 48
 
12502 malex 49
    def _usage(self):
12494 malex 50
        """Prints out a usage summary and exits."""
12493 malex 51
        print "\n", 78*"#"
12491 malex 52
        print """\n
12709 malex 53
Usage: mantisctl.py [Options], version = mantisctl.py 0.4
12491 malex 54
 
12494 malex 55
  ACTION:
12491 malex 56
  -T, --test            Test the script
57
  -L, --list            List the Mantis options
58
  -r, --report          Report a new bug
12494 malex 59
  -h, --help            show this help message and exit
60
 
61
  OPTIONS:
62
  --version             show program's version number and exit
12493 malex 63
  -n, --noconfirm       Do not ask for confirmation before submitting a report
12494 malex 64
  -b BASEURL, --baseurl=BASEURL
65
                        Base URL for a Mantis BTS instance [optional if you set
66
                        it in the script configuration section]. [default: None]
12491 malex 67
  -u USERNAME, --username=USERNAME
68
                        User name [optional if you set it in the script
69
                        itself]. [default: None]
70
  -w PASSWORD, --password=PASSWORD
71
                        User password [optional if you set it in the script
72
                        itself]. [default: None]
73
  -a ASSIGNTO, --assignto=ASSIGNTO
74
                        Assign to [optional]
75
  -c CATEGORY, --category=CATEGORY
76
                        Bug category <mandatory>
77
  -y SEVERITY, --severity=SEVERITY
78
                        Bug severity <mandatory>
79
  -s SUMMARY, --summary=SUMMARY
80
                        Bug summary <mandatory>
81
  -d DESCRIPTION, --desc=DESCRIPTION
82
                        Bug description [optional]
83
  -p PVERSION, --product=PVERSION
84
                        Product version <mandatory>
85
  -t TVERSION, --target=TVERSION
86
                        Target version [optional]
87
        """
88
        sys.exit(1)
89
 
12494 malex 90
    def parseopts(self):
91
        """Parses options and arguments passed to the script
92
        and returns a tuple of a dictionary (opts) and a list (args)."""
12491 malex 93
        from optparse import OptionParser
12709 malex 94
        usage = "%prog [Options], version = %prog 0.4"
95
        op = OptionParser(usage=usage, version='%prog 0.4')
12491 malex 96
        op.add_option("-T", "--test", action="store_true", dest="test", default=False, help="Test the script")
97
        op.add_option("-L", "--list", action="store_true", dest="list", default=False, help="List the Mantis options")
98
        op.add_option("-r", "--report", action="store_true", dest="report", default=False, help="Report a new bug")
12493 malex 99
        op.add_option("-n", "--noconfirm", action="store_true", dest="noconfirm", default=False, help="Do not ask for confirmation before submitting a report")
12495 malex 100
        op.add_option("-b", "--baseurl", dest="baseurl", help="Base URL for a Mantis BTS instance [optional if you set it in the script configuration section].")
101
        op.add_option("-u", "--username", dest="username", help="User name [optional if you set it in the script itself]. [default: %default]")
102
        op.add_option("-w", "--password", dest="password", help="User password [optional if you set it in the script itself]. [default: %default]")
12491 malex 103
        op.add_option("-a", "--assignto", dest="assignto", help="Assign to [optional]")
104
        op.add_option("-c", "--category", dest="category", help="Bug category <mandatory>")
105
        op.add_option("-y", "--severity", dest="severity", help="Bug severity <mandatory>")
106
        op.add_option("-s", "--summary", dest="summary", help="Bug summary <mandatory>")
107
        op.add_option("-d", "--desc", dest="description", help="Bug description [optional]")
108
        op.add_option("-p", "--product", dest="pversion", help="Product version <mandatory>")
109
        op.add_option("-t", "--target", dest="tversion", help="Target version [optional]")
12493 malex 110
        return op.parse_args()
12491 malex 111
 
12494 malex 112
    def _getbaseurl(self):
113
        """Returns the Base URL for a Mantis BTS instance from options or
114
        configuration. Will exit if basurl is not provided either as an option
12495 malex 115
        or in the configuration section."""
12494 malex 116
        if not self.opts.baseurl:
117
            try:
118
                url = baseurl
119
            except:
12495 malex 120
                sys.exit("Base URL is not provided either as an option or in the configuration section.")
12494 malex 121
        else:
122
            url = self.opts.baseurl
12495 malex 123
        return url
12491 malex 124
 
12494 malex 125
    def checkbts(self):
126
        """Checks if the URL for the Mantis BTS instance front page could be
127
        opened and if the title of main page corresponds to the configuration
128
        option 'title'."""
129
        url = self._getbaseurl()
130
        try:
131
            self.br.open(url)
132
            mainpagetitle = self.br.title()
133
        except:
134
            print "Cannot open a Mantis BTS instance main page."
135
            sys.exit(1)
136
        if re.search(basetitle, mainpagetitle):
137
            return True
138
        else:
139
            print "This doesn't seem to be a Mantis BTS instance main page."
140
            sys.exit(1)
12491 malex 141
 
12494 malex 142
    def askconfirm(self):
143
        """Asks for a confirmation to submit a bug report. Exits without
144
        submitting a bug report if 'y' is not provided as an answer."""
145
        msg = "\nYou are about to submit a bug report with the following data:\n"
146
        self.printdata(msg)
147
        yes = ['y', 'Y']
148
        var = raw_input("Press 'y' to submit the report or any other key to abort and hit ENTER: ")
149
        if var in yes:
150
            print "Submitting your bug report..."
151
        else:
152
            print "Exiting without submitting the report..."
153
            sys.exit(0)
12491 malex 154
 
12494 malex 155
    def _printwarning(self, msg, list):
156
        print "\nYou must specify a %s from the following list: \n" % (msg)
157
        print list
158
        sys.exit(1)
12491 malex 159
 
12494 malex 160
    def printdata(self, msg):
161
        """Prints the bug report data either for verification before and after
162
        submission."""
163
        print msg
164
        print "\tCategory: \t\t%s" % self.category
165
        print "\tSeverity: \t\t%s" % self.opts.severity
166
        print "\tAssigned To: \t\t%s" % self.username
167
        print "\tProduct Version: \t%s" % self.pversion
168
        print "\tTarget Version: \t%s" % self.tversion
169
        print "\tSummary: \t\t%s" % self.summary
170
        print "\tDescription: \t\t%s" % self.description
171
 
172
    def _checkcredentials(self):
173
        if self.opts.username:
174
            self.username = self.opts.username
175
        elif username:
176
            self.username = username
177
        else:
178
            print "\nUsername is not specified either in the script configuration or on the command line."
179
            self._usage()
180
        if self.opts.password:
181
            self.password = self.opts.password
182
        elif password:
183
            self.password = password
184
        else:
185
            print "\nPassword is not specified either in the script or on the command line."
186
            self._usage()
187
 
188
    def login(self):
189
        """Performs logging into a Mantis BTS instance as an authenticated
190
        reporter."""
191
        self._checkcredentials()
12491 malex 192
        self.loginlink = self.br.links(text_regex=re.compile("Login")).next()
193
        self.br.follow_link(self.loginlink)
194
        self.br.select_form(name="login_form")
12494 malex 195
        self.br.form["username"] = self.username
196
        self.br.form["password"] = self.password
12491 malex 197
        self.br.submit()
198
 
12494 malex 199
    def _gethandlers(self, response):
200
        """Returns a dictionary of 'Assign To' names and ids directly from the bug report page."""
201
        response.seek(0)
202
        handlersoup = BeautifulSoup(response)
203
        selects = handlersoup.findAll('select', { "name" : "handler_id" })[0].findAll("option")
204
        handlers = {}
205
        for select in selects:
206
            handler_id = select.attrs[0][1].strip().encode('utf8')
207
            if len (select.contents) > 0:
208
                handler_login = select.contents[0].strip().encode('utf8')
209
            else:
210
                handler_login = ""
211
            handlers[handler_login] = handler_id
212
        return handlers
12491 malex 213
 
12494 malex 214
    def _getseverities(self, response):
215
        """Returns a dictionary of 'Severity' names and ids directly from the bug report page."""
216
        response.seek(0)
217
        severitysoup = BeautifulSoup(response)
218
        selects = severitysoup.findAll('select', { "name" : "severity" })[0].findAll("option")
219
        severities = {}
220
        for select in selects:
221
            severity_id = select.attrs[0][1].strip().encode('utf8')
222
            if len (select.contents) > 0:
223
                severity_name = select.contents[0].strip().encode('utf8')
224
            else:
225
                severity_name = ""
226
            severities[severity_name] = severity_id
227
        return severities
12491 malex 228
 
12494 malex 229
    def _getcategories(self):
230
        """Returns a list of bug categories directly from the bug reporting page."""
231
        category_control = self.br.form.find_control("category")
232
        category_list = []
233
        for i in category_control.items:
234
            category_list.append(i.name)
235
        return category_list
236
 
237
    def _getpversions(self):
238
        """Returns a list of product versions directly from the bug reporting page."""
239
        pversion_control = self.br.form.find_control("product_version")
240
        pversion_list = []
241
        for i in pversion_control.items:
242
            pversion_list.append(i.name)
243
        return pversion_list
244
 
245
    def _gettversions(self):
246
        """Returns a list of target versions directly from the bug reporting page."""
247
        tversion_control = self.br.form.find_control("target_version")
248
        tversion_list = []
249
        for i in tversion_control.items:
250
            tversion_list.append(i.name)
251
        return tversion_list
252
 
253
    def setreportdata(self):
254
        """Handles filling out the bug report form."""
255
        reportlink = self.br.links(text_regex=re.compile("Report Issue")).next()
256
        response = self.br.follow_link(reportlink)
257
        self.handlers = self._gethandlers(response)
258
        self.severities = self._getseverities(response)
259
        self.br.select_form(name="report_bug_form")
260
        self.categories = self._getcategories()
261
        self.pversions = self._getpversions()
262
        self.tversions = self._gettversions()
263
 
264
    def checkreportopts(self):
265
        """Verifies the validity of options passed to the script."""
266
        # summary
12491 malex 267
        if not self.opts.summary:
268
            sys.exit("You must provide a bug summary!")
12494 malex 269
        else:
270
            self.summary = self.opts.summary
271
        # description
272
        if self.opts.description and len(self.opts.description.strip()) > 1:
273
            self.description = self.opts.description
274
        else:
275
            self.description = self.opts.summary
276
        # handler
12491 malex 277
        if self.opts.assignto and len(self.opts.assignto) > 0:
278
            if self.opts.assignto not in self.handlers.keys():
12494 malex 279
                self._printwarning("developer to assign bug to", self.handlers.keys())
12491 malex 280
            else:
281
                self.handler = str(self.handlers[self.opts.assignto])
282
        else:
283
            self.handler = '0'
12494 malex 284
        # category
12491 malex 285
        if not self.opts.category:
12494 malex 286
            self._printwarning("category", self.categories)
12491 malex 287
        else:
12494 malex 288
            if self.opts.category in self.categories:
289
                self.category = self.opts.category
12491 malex 290
            else:
12494 malex 291
                self._printwarning("category", self.categories)
292
        # severity
12709 malex 293
        if not self.opts.severity:
294
            self._printwarning("severity", self.severities.keys())
295
            sys.exit(1)
12494 malex 296
        if self.opts.severity and len(self.opts.severity) > 0:
12491 malex 297
            if self.opts.severity in self.severities.keys():
12494 malex 298
                self.severity = str(self.severities[self.opts.severity])
12491 malex 299
            else:
12494 malex 300
                self._printwarning("severity", self.severities.keys())
301
        # product version
12491 malex 302
        if not self.opts.pversion:
12494 malex 303
            self._printwarning("product version", self.pversions)
12491 malex 304
        else:
12494 malex 305
            if self.opts.pversion in self.pversions:
306
                self.pversion = self.opts.pversion
12491 malex 307
            else:
12494 malex 308
                self._printwarning("product version", self.pversions)
309
        # target version
310
        if not self.opts.tversion:
311
            self._printwarning("target version", self.tversions)
312
        else:
313
            if self.opts.tversion in self.tversions:
314
                self.tversion = self.opts.tversion
12491 malex 315
            else:
12494 malex 316
                self._printwarning("target version", self.tversions)
12491 malex 317
 
12494 malex 318
    def filloutreportform(self):
319
        """Fills out required controls on the bug reporting form."""
320
        self.br.form["category"] = [self.category]
12491 malex 321
        self.br.form["severity"] = [self.severity]
322
        self.br.form["handler_id"] = [self.handler]
12494 malex 323
        self.br.form["product_version"] = [self.pversion]
324
        if self.tversion:
325
            self.br.form["target_version"] = [self.tversion]
326
        self.br.form["summary"] = self.summary
327
        self.br.form["description"] = self.description
12491 malex 328
 
12502 malex 329
    def comparebugdata(self, soup, data):
330
        bugs = soup.findAll("table", id="buglist")[0].findAll("tr")[3:-1]
12709 malex 331
        bugdata = bugs[0].findAll("td")
332
        bugno = bugdata[3].contents[0].contents[0].strip().encode('utf-8')[-4:]
333
        print "bug number: %s" % bugno
334
        try:
335
            severity = bugdata[8].contents[0].strip().encode('utf-8')
336
        except:
337
            severity = None
338
        print "severity: %s" % severity
339
        pnumber = bugdata[4].contents[0].strip().encode('utf-8')
340
        print "product number: %s" % pnumber
341
        reporter = bugdata[9].contents[0].strip().encode('utf-8')
342
        print "reporter: %s" % reporter
343
        if [reporter, severity, pnumber] == data:
344
            return bugno
12502 malex 345
        return None
346
 
12491 malex 347
    def reportbug(self):
12494 malex 348
        """Handles submission of a bug report."""
349
        self.setreportdata()
350
        self.checkreportopts()
351
        self.filloutreportform()
12493 malex 352
        if not self.opts.noconfirm:
12494 malex 353
            self.askconfirm()
12502 malex 354
#        try:
12495 malex 355
#        import sys, logging
356
#        logger = logging.getLogger("mechanize")
357
#        logger.addHandler(logging.StreamHandler(sys.stdout))
358
#        logger.setLevel(logging.DEBUG)
12502 malex 359
        self.br.set_handle_refresh(True, max_time=2.0, honor_time=True)
360
        response = self.br.submit()
361
        response.seek(0)
362
        reportsoup = BeautifulSoup(response)
363
#        print reportsoup
364
#        except:
365
#            pass
12491 malex 366
        self.br.close()
12502 malex 367
        data = [self.username, self.opts.severity, self.pversion]
368
        bugno = self.comparebugdata(reportsoup, data)
369
        if bugno != None:
370
            msg = "\nBug report number %s submission appears to be successful! The following data have been submitted:\n" % bugno
371
        else:
372
            msg = "\nCannot find the bug report. Please check the BTS directly.\n"
12494 malex 373
        self.printdata(msg)
12491 malex 374
        sys.exit(0)
375
 
12494 malex 376
    def listopts(self):
377
        """Lists options passed to the script and those obtained directly from
378
        a Mantis BTS instance."""
379
        self.setreportdata()
380
        self.checkreportopts()
381
        print "Severities: \n", "\t", self.severities.keys(), "\n"
382
        print "Categories: \n", "\t", self.categories, "\n"
383
        print "Developers: \n", "\t", self.handlers.keys(), "\n"
384
        print "Product Versions: \n", "\t", self.pversions, "\n"
12491 malex 385
 
12494 malex 386
    def runchoice(self):
387
        """Process a run choice such as list options, test the script, show
388
        help, or submit a bug report or print usage summary and exit if no
389
        action has been specified."""
12491 malex 390
        if self.opts.list:
391
            print "Listing the Mantis options...\n"
12494 malex 392
            self.listopts()
393
        elif self.opts.test:
394
            print "\nTesting the script...\n",
395
            self.setreportdata()
396
            self.checkreportopts()
397
            print "\nTest run completed without errors.\n"
398
            sys.exit(0)
399
        elif self.opts.report:
400
            print "\nSubmitting a new bug report.\n"
12491 malex 401
            self.reportbug()
12494 malex 402
        else:
403
            print """\nNo action such as -r (submit a bug report), -L (list
404
            options), or -T (test run) has been specified. Exiting...\n"""
405
            self._usage()
12491 malex 406
 
12494 malex 407
    def dorun(self):
408
        """Main controller of the script. It will be called if the script is
409
        run from a command line or can be run by an external script. No other
410
        interface is available."""
411
        self.checkbts()
412
        self.login()
413
        self.runchoice()
414
 
12491 malex 415
if __name__=='__main__':
12494 malex 416
    """Check if the script is run from a command line, initialize the main
417
    class and either show usage summary or run the main controller method."""
12491 malex 418
    if len(sys.argv) == 1:
419
        sys.argv.append("--help")
12493 malex 420
        browser = Mantisctl()
12491 malex 421
    else:
422
        browser = Mantisctl()
423
        browser.dorun()
424
# vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: