Subversion Repositories Scribus

Rev

Rev 12494 | Rev 12502 | 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)
43
        self.br.set_handle_referer(True)
12495 malex 44
        self.br.set_handle_refresh(True, max_time=10.0, honor_time=False)
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
 
12494 malex 49
    def usage(self):
50
        """Prints out a usage summary and exits."""
12493 malex 51
        print "\n", 78*"#"
12491 malex 52
        print """\n
12494 malex 53
Usage: mantisctl.py [Options], version = mantisctl.py 0.3
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
12494 malex 94
        usage = "%prog [Options], version = %prog 0.3"
95
        op = OptionParser(usage=usage, version='%prog 0.3')
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
        # Get the base url from options or configuration section
130
        url = self._getbaseurl()
131
        try:
132
            self.br.open(url)
133
            mainpagetitle = self.br.title()
134
        except:
135
            print "Cannot open a Mantis BTS instance main page."
136
            sys.exit(1)
137
        # basetitle is defined in the configuration section
138
        if re.search(basetitle, mainpagetitle):
139
            return True
140
        else:
141
            print "This doesn't seem to be a Mantis BTS instance main page."
142
            sys.exit(1)
12491 malex 143
 
12494 malex 144
    def askconfirm(self):
145
        """Asks for a confirmation to submit a bug report. Exits without
146
        submitting a bug report if 'y' is not provided as an answer."""
147
        msg = "\nYou are about to submit a bug report with the following data:\n"
148
        self.printdata(msg)
149
        yes = ['y', 'Y']
150
        var = raw_input("Press 'y' to submit the report or any other key to abort and hit ENTER: ")
151
        print var
152
        if var in yes:
153
            print "Submitting your bug report..."
154
        else:
155
            print "Exiting without submitting the report..."
156
            sys.exit(0)
12491 malex 157
 
12494 malex 158
    def _printwarning(self, msg, list):
159
        print "\nYou must specify a %s from the following list: \n" % (msg)
160
        print list
161
        sys.exit(1)
12491 malex 162
 
12494 malex 163
    def printdata(self, msg):
164
        """Prints the bug report data either for verification before and after
165
        submission."""
166
        print msg
167
        print "\tCategory: \t\t%s" % self.category
168
        print "\tSeverity: \t\t%s" % self.opts.severity
169
        print "\tAssigned To: \t\t%s" % self.username
170
        print "\tProduct Version: \t%s" % self.pversion
171
        print "\tTarget Version: \t%s" % self.tversion
172
        print "\tSummary: \t\t%s" % self.summary
173
        print "\tDescription: \t\t%s" % self.description
174
 
175
    def _checkcredentials(self):
176
        if self.opts.username:
177
            self.username = self.opts.username
178
        elif username:
179
            self.username = username
180
        else:
181
            print "\nUsername is not specified either in the script configuration or on the command line."
182
            self._usage()
183
        if self.opts.password:
184
            self.password = self.opts.password
185
        elif password:
186
            self.password = password
187
        else:
188
            print "\nPassword is not specified either in the script or on the command line."
189
            self._usage()
190
 
191
    def login(self):
192
        """Performs logging into a Mantis BTS instance as an authenticated
193
        reporter."""
194
        # Check if we have the credentials to login into a Mantis BTS instance.
195
        self._checkcredentials()
12491 malex 196
        self.loginlink = self.br.links(text_regex=re.compile("Login")).next()
197
        self.br.follow_link(self.loginlink)
198
        self.br.select_form(name="login_form")
12494 malex 199
        self.br.form["username"] = self.username
200
        self.br.form["password"] = self.password
12491 malex 201
        self.br.submit()
202
 
12494 malex 203
    def _gethandlers(self, response):
204
        """Returns a dictionary of 'Assign To' names and ids directly from the bug report page."""
205
        response.seek(0)
206
        handlersoup = BeautifulSoup(response)
207
        selects = handlersoup.findAll('select', { "name" : "handler_id" })[0].findAll("option")
208
        handlers = {}
209
        for select in selects:
210
            handler_id = select.attrs[0][1].strip().encode('utf8')
211
            if len (select.contents) > 0:
212
                handler_login = select.contents[0].strip().encode('utf8')
213
            else:
214
                handler_login = ""
215
            handlers[handler_login] = handler_id
216
        return handlers
12491 malex 217
 
12494 malex 218
    def _getseverities(self, response):
219
        """Returns a dictionary of 'Severity' names and ids directly from the bug report page."""
220
        response.seek(0)
221
        severitysoup = BeautifulSoup(response)
222
        selects = severitysoup.findAll('select', { "name" : "severity" })[0].findAll("option")
223
        severities = {}
224
        for select in selects:
225
            severity_id = select.attrs[0][1].strip().encode('utf8')
226
            if len (select.contents) > 0:
227
                severity_name = select.contents[0].strip().encode('utf8')
228
            else:
229
                severity_name = ""
230
            severities[severity_name] = severity_id
231
        return severities
12491 malex 232
 
12494 malex 233
    def _getcategories(self):
234
        """Returns a list of bug categories directly from the bug reporting page."""
235
        category_control = self.br.form.find_control("category")
236
        category_list = []
237
        for i in category_control.items:
238
            category_list.append(i.name)
239
        return category_list
240
 
241
    def _getpversions(self):
242
        """Returns a list of product versions directly from the bug reporting page."""
243
        pversion_control = self.br.form.find_control("product_version")
244
        pversion_list = []
245
        for i in pversion_control.items:
246
            pversion_list.append(i.name)
247
        return pversion_list
248
 
249
    def _gettversions(self):
250
        """Returns a list of target versions directly from the bug reporting page."""
251
        tversion_control = self.br.form.find_control("target_version")
252
        tversion_list = []
253
        for i in tversion_control.items:
254
            tversion_list.append(i.name)
255
        return tversion_list
256
 
257
    def setreportdata(self):
258
        """Handles filling out the bug report form."""
259
        reportlink = self.br.links(text_regex=re.compile("Report Issue")).next()
260
        response = self.br.follow_link(reportlink)
261
        self.handlers = self._gethandlers(response)
262
        self.severities = self._getseverities(response)
263
        self.br.select_form(name="report_bug_form")
264
        self.categories = self._getcategories()
265
        self.pversions = self._getpversions()
266
        self.tversions = self._gettversions()
267
 
268
    def checkreportopts(self):
269
        """Verifies the validity of options passed to the script."""
270
        # summary
12491 malex 271
        if not self.opts.summary:
272
            sys.exit("You must provide a bug summary!")
12494 malex 273
        else:
274
            self.summary = self.opts.summary
275
        # description
276
        if self.opts.description and len(self.opts.description.strip()) > 1:
277
            self.description = self.opts.description
278
        else:
279
            self.description = self.opts.summary
280
        # handler
12491 malex 281
        if self.opts.assignto and len(self.opts.assignto) > 0:
282
            if self.opts.assignto not in self.handlers.keys():
12494 malex 283
                self._printwarning("developer to assign bug to", self.handlers.keys())
12491 malex 284
            else:
285
                self.handler = str(self.handlers[self.opts.assignto])
286
        else:
287
            self.handler = '0'
12494 malex 288
        # category
12491 malex 289
        if not self.opts.category:
12494 malex 290
            self._printwarning("category", self.categories)
12491 malex 291
        else:
12494 malex 292
            if self.opts.category in self.categories:
293
                self.category = self.opts.category
12491 malex 294
            else:
12494 malex 295
                self._printwarning("category", self.categories)
296
        # severity
297
        if self.opts.severity and len(self.opts.severity) > 0:
12491 malex 298
            if self.opts.severity in self.severities.keys():
12494 malex 299
                self.severity = str(self.severities[self.opts.severity])
12491 malex 300
            else:
12494 malex 301
                self._printwarning("severity", self.severities.keys())
302
        # product version
12491 malex 303
        if not self.opts.pversion:
12494 malex 304
            self._printwarning("product version", self.pversions)
12491 malex 305
        else:
12494 malex 306
            if self.opts.pversion in self.pversions:
307
                self.pversion = self.opts.pversion
12491 malex 308
            else:
12494 malex 309
                self._printwarning("product version", self.pversions)
310
        # target version
311
        if not self.opts.tversion:
312
            self._printwarning("target version", self.tversions)
313
        else:
314
            if self.opts.tversion in self.tversions:
315
                self.tversion = self.opts.tversion
12491 malex 316
            else:
12494 malex 317
                self._printwarning("target version", self.tversions)
12491 malex 318
 
12494 malex 319
    def filloutreportform(self):
320
        """Fills out required controls on the bug reporting form."""
321
        self.br.form["category"] = [self.category]
12491 malex 322
        self.br.form["severity"] = [self.severity]
323
        self.br.form["handler_id"] = [self.handler]
12494 malex 324
        self.br.form["product_version"] = [self.pversion]
325
        if self.tversion:
326
            self.br.form["target_version"] = [self.tversion]
327
        self.br.form["summary"] = self.summary
328
        self.br.form["description"] = self.description
12491 malex 329
 
330
    def reportbug(self):
12494 malex 331
        """Handles submission of a bug report."""
332
        self.setreportdata()
333
        self.checkreportopts()
334
        # depends on self.checkreportopts as it sets all self.var(s)
335
        self.filloutreportform()
12493 malex 336
        if not self.opts.noconfirm:
12494 malex 337
            self.askconfirm()
12491 malex 338
        try:
12495 malex 339
            #TODO
340
            response = self.br.submit()
341
#        response.seek(0)
342
#            reportsoup = BeautifulSoup(response)
343
#            print reportsoup
12491 malex 344
        except:
345
            pass
12495 malex 346
#        import sys, logging
347
#        logger = logging.getLogger("mechanize")
348
#        logger.addHandler(logging.StreamHandler(sys.stdout))
349
#        logger.setLevel(logging.DEBUG)
12491 malex 350
        self.br.close()
12494 malex 351
        msg = "\nBug report submission appears to be successful! The following data have been submitted:\n"
352
        self.printdata(msg)
12491 malex 353
        sys.exit(0)
354
 
12494 malex 355
    def listopts(self):
356
        """Lists options passed to the script and those obtained directly from
357
        a Mantis BTS instance."""
358
        self.setreportdata()
359
        self.checkreportopts()
360
        print "Severities: \n", "\t", self.severities.keys(), "\n"
361
        print "Categories: \n", "\t", self.categories, "\n"
362
        print "Developers: \n", "\t", self.handlers.keys(), "\n"
363
        print "Product Versions: \n", "\t", self.pversions, "\n"
12491 malex 364
 
12494 malex 365
    def runchoice(self):
366
        """Process a run choice such as list options, test the script, show
367
        help, or submit a bug report or print usage summary and exit if no
368
        action has been specified."""
12491 malex 369
        if self.opts.list:
370
            print "Listing the Mantis options...\n"
12494 malex 371
            self.listopts()
372
        elif self.opts.test:
373
            print "\nTesting the script...\n",
374
            self.setreportdata()
375
            self.checkreportopts()
376
            print "\nTest run completed without errors.\n"
377
            sys.exit(0)
378
        elif self.opts.report:
379
            print "\nSubmitting a new bug report.\n"
12491 malex 380
            self.reportbug()
12494 malex 381
        else:
382
            print """\nNo action such as -r (submit a bug report), -L (list
383
            options), or -T (test run) has been specified. Exiting...\n"""
384
            self._usage()
12491 malex 385
 
12494 malex 386
    def dorun(self):
387
        """Main controller of the script. It will be called if the script is
388
        run from a command line or can be run by an external script. No other
389
        interface is available."""
390
        # Check if we can access the main page of a Mantis BTS instance.
391
        self.checkbts()
392
        # Login into a Mantis BTS instance.
393
        self.login()
394
        # Select and run an action.
395
        self.runchoice()
396
 
12491 malex 397
if __name__=='__main__':
12494 malex 398
    """Check if the script is run from a command line, initialize the main
399
    class and either show usage summary or run the main controller method."""
12491 malex 400
    if len(sys.argv) == 1:
401
        sys.argv.append("--help")
12493 malex 402
        browser = Mantisctl()
12491 malex 403
    else:
404
        browser = Mantisctl()
405
        browser.dorun()
406
# vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: