Subversion Repositories Scribus

Rev

Rev 12493 | Rev 12495 | Go to most recent revision | 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"
30
#baseurl = None
31
#username = None
32
#password = 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)
12494 malex 43
        self.br.set_handle_refresh(True, max_time=30.0, honor_time=False)
12491 malex 44
        self.br.set_handle_referer(True)
12494 malex 45
        (self.opts, self.args) = self.parseopts()
12491 malex 46
 
12494 malex 47
    def usage(self):
48
        """Prints out a usage summary and exits."""
12493 malex 49
        print "\n", 78*"#"
12491 malex 50
        print """\n
12494 malex 51
Usage: mantisctl.py [Options], version = mantisctl.py 0.3
12491 malex 52
 
12494 malex 53
  ACTION:
12491 malex 54
  -T, --test            Test the script
55
  -L, --list            List the Mantis options
56
  -r, --report          Report a new bug
12494 malex 57
  -h, --help            show this help message and exit
58
 
59
  OPTIONS:
60
  --version             show program's version number and exit
12493 malex 61
  -n, --noconfirm       Do not ask for confirmation before submitting a report
12494 malex 62
  -b BASEURL, --baseurl=BASEURL
63
                        Base URL for a Mantis BTS instance [optional if you set
64
                        it in the script configuration section]. [default: None]
12491 malex 65
  -u USERNAME, --username=USERNAME
66
                        User name [optional if you set it in the script
67
                        itself]. [default: None]
68
  -w PASSWORD, --password=PASSWORD
69
                        User password [optional if you set it in the script
70
                        itself]. [default: None]
71
  -a ASSIGNTO, --assignto=ASSIGNTO
72
                        Assign to [optional]
73
  -c CATEGORY, --category=CATEGORY
74
                        Bug category <mandatory>
75
  -y SEVERITY, --severity=SEVERITY
76
                        Bug severity <mandatory>
77
  -s SUMMARY, --summary=SUMMARY
78
                        Bug summary <mandatory>
79
  -d DESCRIPTION, --desc=DESCRIPTION
80
                        Bug description [optional]
81
  -p PVERSION, --product=PVERSION
82
                        Product version <mandatory>
83
  -t TVERSION, --target=TVERSION
84
                        Target version [optional]
85
        """
86
        sys.exit(1)
87
 
12494 malex 88
    def parseopts(self):
89
        """Parses options and arguments passed to the script
90
        and returns a tuple of a dictionary (opts) and a list (args)."""
12491 malex 91
        from optparse import OptionParser
12494 malex 92
        usage = "%prog [Options], version = %prog 0.3"
93
        op = OptionParser(usage=usage, version='%prog 0.3')
12491 malex 94
        op.add_option("-T", "--test", action="store_true", dest="test", default=False, help="Test the script")
95
        op.add_option("-L", "--list", action="store_true", dest="list", default=False, help="List the Mantis options")
96
        op.add_option("-r", "--report", action="store_true", dest="report", default=False, help="Report a new bug")
12493 malex 97
        op.add_option("-n", "--noconfirm", action="store_true", dest="noconfirm", default=False, help="Do not ask for confirmation before submitting a report")
12494 malex 98
        op.add_option("-b", "--baseurl", dest="baseurl", default=baseurl, help="Base URL for a Mantis BTS instance [optional if you set it in the script configuration section]. [default: %default]")
99
        op.add_option("-u", "--username", dest="username", default=username, help="User name [optional if you set it in the script itself]. [default: %default]")
100
        op.add_option("-w", "--password", dest="password", default=password, help="User password [optional if you set it in the script itself]. [default: %default]")
12491 malex 101
        op.add_option("-a", "--assignto", dest="assignto", help="Assign to [optional]")
102
        op.add_option("-c", "--category", dest="category", help="Bug category <mandatory>")
103
        op.add_option("-y", "--severity", dest="severity", help="Bug severity <mandatory>")
104
        op.add_option("-s", "--summary", dest="summary", help="Bug summary <mandatory>")
105
        op.add_option("-d", "--desc", dest="description", help="Bug description [optional]")
106
        op.add_option("-p", "--product", dest="pversion", help="Product version <mandatory>")
107
        op.add_option("-t", "--target", dest="tversion", help="Target version [optional]")
12493 malex 108
        return op.parse_args()
12491 malex 109
 
12494 malex 110
    def _getbaseurl(self):
111
        """Returns the Base URL for a Mantis BTS instance from options or
112
        configuration. Will exit if basurl is not provided either as an option
113
        or in the confguration section."""
114
        if not self.opts.baseurl:
115
            try:
116
                url = baseurl
117
            except:
118
                sys.exit("Base URL is not provided either as an option or in the confguration section.")
119
        else:
120
            url = self.opts.baseurl
121
        return baseurl
12491 malex 122
 
12494 malex 123
    def checkbts(self):
124
        """Checks if the URL for the Mantis BTS instance front page could be
125
        opened and if the title of main page corresponds to the configuration
126
        option 'title'."""
127
        # Get the base url from options or configuration section
128
        url = self._getbaseurl()
129
        try:
130
            self.br.open(url)
131
            mainpagetitle = self.br.title()
132
        except:
133
            print "Cannot open a Mantis BTS instance main page."
134
            sys.exit(1)
135
        # basetitle is defined in the configuration section
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
        print var
150
        if var in yes:
151
            print "Submitting your bug report..."
152
        else:
153
            print "Exiting without submitting the report..."
154
            sys.exit(0)
12491 malex 155
 
12494 malex 156
    def _printwarning(self, msg, list):
157
        print "\nYou must specify a %s from the following list: \n" % (msg)
158
        print list
159
        sys.exit(1)
12491 malex 160
 
12494 malex 161
    def printdata(self, msg):
162
        """Prints the bug report data either for verification before and after
163
        submission."""
164
        print msg
165
        print "\tCategory: \t\t%s" % self.category
166
        print "\tSeverity: \t\t%s" % self.opts.severity
167
        print "\tAssigned To: \t\t%s" % self.username
168
        print "\tProduct Version: \t%s" % self.pversion
169
        print "\tTarget Version: \t%s" % self.tversion
170
        print "\tSummary: \t\t%s" % self.summary
171
        print "\tDescription: \t\t%s" % self.description
172
 
173
    def _checkcredentials(self):
174
        if self.opts.username:
175
            self.username = self.opts.username
176
        elif username:
177
            self.username = username
178
        else:
179
            print "\nUsername is not specified either in the script configuration or on the command line."
180
            self._usage()
181
        if self.opts.password:
182
            self.password = self.opts.password
183
        elif password:
184
            self.password = password
185
        else:
186
            print "\nPassword is not specified either in the script or on the command line."
187
            self._usage()
188
 
189
    def login(self):
190
        """Performs logging into a Mantis BTS instance as an authenticated
191
        reporter."""
192
        # Check if we have the credentials to login into a Mantis BTS instance.
193
        self._checkcredentials()
12491 malex 194
        self.loginlink = self.br.links(text_regex=re.compile("Login")).next()
195
        self.br.follow_link(self.loginlink)
196
        self.br.select_form(name="login_form")
12494 malex 197
        self.br.form["username"] = self.username
198
        self.br.form["password"] = self.password
12491 malex 199
        self.br.submit()
200
 
12494 malex 201
    def _gethandlers(self, response):
202
        """Returns a dictionary of 'Assign To' names and ids directly from the bug report page."""
203
        response.seek(0)
204
        handlersoup = BeautifulSoup(response)
205
        selects = handlersoup.findAll('select', { "name" : "handler_id" })[0].findAll("option")
206
        handlers = {}
207
        for select in selects:
208
            handler_id = select.attrs[0][1].strip().encode('utf8')
209
            if len (select.contents) > 0:
210
                handler_login = select.contents[0].strip().encode('utf8')
211
            else:
212
                handler_login = ""
213
            handlers[handler_login] = handler_id
214
        return handlers
12491 malex 215
 
12494 malex 216
    def _getseverities(self, response):
217
        """Returns a dictionary of 'Severity' names and ids directly from the bug report page."""
218
        response.seek(0)
219
        severitysoup = BeautifulSoup(response)
220
        selects = severitysoup.findAll('select', { "name" : "severity" })[0].findAll("option")
221
        severities = {}
222
        for select in selects:
223
            severity_id = select.attrs[0][1].strip().encode('utf8')
224
            if len (select.contents) > 0:
225
                severity_name = select.contents[0].strip().encode('utf8')
226
            else:
227
                severity_name = ""
228
            severities[severity_name] = severity_id
229
        return severities
12491 malex 230
 
12494 malex 231
    def _getcategories(self):
232
        """Returns a list of bug categories directly from the bug reporting page."""
233
        category_control = self.br.form.find_control("category")
234
        category_list = []
235
        for i in category_control.items:
236
            category_list.append(i.name)
237
        return category_list
238
 
239
    def _getpversions(self):
240
        """Returns a list of product versions directly from the bug reporting page."""
241
        pversion_control = self.br.form.find_control("product_version")
242
        pversion_list = []
243
        for i in pversion_control.items:
244
            pversion_list.append(i.name)
245
        return pversion_list
246
 
247
    def _gettversions(self):
248
        """Returns a list of target versions directly from the bug reporting page."""
249
        tversion_control = self.br.form.find_control("target_version")
250
        tversion_list = []
251
        for i in tversion_control.items:
252
            tversion_list.append(i.name)
253
        return tversion_list
254
 
255
    def setreportdata(self):
256
        """Handles filling out the bug report form."""
257
        reportlink = self.br.links(text_regex=re.compile("Report Issue")).next()
258
        response = self.br.follow_link(reportlink)
259
        self.handlers = self._gethandlers(response)
260
        self.severities = self._getseverities(response)
261
        self.br.select_form(name="report_bug_form")
262
        self.categories = self._getcategories()
263
        self.pversions = self._getpversions()
264
        self.tversions = self._gettversions()
265
 
266
    def checkreportopts(self):
267
        """Verifies the validity of options passed to the script."""
268
        # summary
12491 malex 269
        if not self.opts.summary:
270
            sys.exit("You must provide a bug summary!")
12494 malex 271
        else:
272
            self.summary = self.opts.summary
273
        # description
274
        if self.opts.description and len(self.opts.description.strip()) > 1:
275
            self.description = self.opts.description
276
        else:
277
            self.description = self.opts.summary
278
        # handler
12491 malex 279
        if self.opts.assignto and len(self.opts.assignto) > 0:
280
            if self.opts.assignto not in self.handlers.keys():
12494 malex 281
                self._printwarning("developer to assign bug to", self.handlers.keys())
12491 malex 282
            else:
283
                self.handler = str(self.handlers[self.opts.assignto])
284
        else:
285
            self.handler = '0'
12494 malex 286
        # category
12491 malex 287
        if not self.opts.category:
12494 malex 288
            self._printwarning("category", self.categories)
12491 malex 289
        else:
12494 malex 290
            if self.opts.category in self.categories:
291
                self.category = self.opts.category
12491 malex 292
            else:
12494 malex 293
                self._printwarning("category", self.categories)
294
        # severity
295
        if self.opts.severity and len(self.opts.severity) > 0:
12491 malex 296
            if self.opts.severity in self.severities.keys():
12494 malex 297
                self.severity = str(self.severities[self.opts.severity])
12491 malex 298
            else:
12494 malex 299
                self._printwarning("severity", self.severities.keys())
300
        # product version
12491 malex 301
        if not self.opts.pversion:
12494 malex 302
            self._printwarning("product version", self.pversions)
12491 malex 303
        else:
12494 malex 304
            if self.opts.pversion in self.pversions:
305
                self.pversion = self.opts.pversion
12491 malex 306
            else:
12494 malex 307
                self._printwarning("product version", self.pversions)
308
        # target version
309
        if not self.opts.tversion:
310
            self._printwarning("target version", self.tversions)
311
        else:
312
            if self.opts.tversion in self.tversions:
313
                self.tversion = self.opts.tversion
12491 malex 314
            else:
12494 malex 315
                self._printwarning("target version", self.tversions)
12491 malex 316
 
12494 malex 317
    def filloutreportform(self):
318
        """Fills out required controls on the bug reporting form."""
319
        self.br.form["category"] = [self.category]
12491 malex 320
        self.br.form["severity"] = [self.severity]
321
        self.br.form["handler_id"] = [self.handler]
12494 malex 322
        self.br.form["product_version"] = [self.pversion]
323
        if self.tversion:
324
            self.br.form["target_version"] = [self.tversion]
325
        self.br.form["summary"] = self.summary
326
        self.br.form["description"] = self.description
12491 malex 327
 
328
    def reportbug(self):
12494 malex 329
        """Handles submission of a bug report."""
330
        self.setreportdata()
331
        self.checkreportopts()
332
        # depends on self.checkreportopts as it sets all self.var(s)
333
        self.filloutreportform()
12493 malex 334
        if not self.opts.noconfirm:
12494 malex 335
            self.askconfirm()
12491 malex 336
        try:
337
            self.br.submit()
338
        except:
339
            pass
340
        self.br.close()
12494 malex 341
        msg = "\nBug report submission appears to be successful! The following data have been submitted:\n"
342
        self.printdata(msg)
12491 malex 343
        sys.exit(0)
344
 
12494 malex 345
    def listopts(self):
346
        """Lists options passed to the script and those obtained directly from
347
        a Mantis BTS instance."""
348
        self.setreportdata()
349
        self.checkreportopts()
350
        print "Severities: \n", "\t", self.severities.keys(), "\n"
351
        print "Categories: \n", "\t", self.categories, "\n"
352
        print "Developers: \n", "\t", self.handlers.keys(), "\n"
353
        print "Product Versions: \n", "\t", self.pversions, "\n"
12491 malex 354
 
12494 malex 355
    def runchoice(self):
356
        """Process a run choice such as list options, test the script, show
357
        help, or submit a bug report or print usage summary and exit if no
358
        action has been specified."""
12491 malex 359
        if self.opts.list:
360
            print "Listing the Mantis options...\n"
12494 malex 361
            self.listopts()
362
        elif self.opts.test:
363
            print "\nTesting the script...\n",
364
            self.setreportdata()
365
            self.checkreportopts()
366
            print "\nTest run completed without errors.\n"
367
            sys.exit(0)
368
        elif self.opts.report:
369
            print "\nSubmitting a new bug report.\n"
12491 malex 370
            self.reportbug()
12494 malex 371
        else:
372
            print """\nNo action such as -r (submit a bug report), -L (list
373
            options), or -T (test run) has been specified. Exiting...\n"""
374
            self._usage()
12491 malex 375
 
12494 malex 376
    def dorun(self):
377
        """Main controller of the script. It will be called if the script is
378
        run from a command line or can be run by an external script. No other
379
        interface is available."""
380
        # Check if we can access the main page of a Mantis BTS instance.
381
        self.checkbts()
382
        # Login into a Mantis BTS instance.
383
        self.login()
384
        # Select and run an action.
385
        self.runchoice()
386
 
12491 malex 387
if __name__=='__main__':
12494 malex 388
    """Check if the script is run from a command line, initialize the main
389
    class and either show usage summary or run the main controller method."""
12491 malex 390
    if len(sys.argv) == 1:
391
        sys.argv.append("--help")
12493 malex 392
        browser = Mantisctl()
12491 malex 393
    else:
394
        browser = Mantisctl()
395
        browser.dorun()
396
# vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: