Rev 12494 |
Rev 12502 |
Go to most recent revision |
Blame |
Compare with Previous |
Last modification |
View Log
| RSS feed
#!/usr/bin/env python
"""This script allows command line bug report submission to a Mantis
bug tracking system (http://www.mantisbt.org/)."""
###
# Copyright (c) 2008, Oleksandr Moskalenko
# All rights reserved.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
###
import re, sys, logging
from mechanize import Browser
from BeautifulSoup import BeautifulSoup
##############################################################################
# USER CONFIGURATION - edit for your site if necessary. #
##############################################################################
basetitle = "^Mantis Issue Tracker"
#baseurl = None
#username = None
#password = None
##############################################################################
class Mantisctl:
"""This class handles command line Mantis Bug Tracking System bug report
submission. a mechanize.Browser() instance is initialized and used to
control the site navigation and bug report submission. dorun is
the only public method you really need to run to submit a bug report."""
def __init__(self):
self.br = Browser()
self.br.set_handle_redirect(True)
self.br.set_handle_referer(True)
self.br.set_handle_refresh(True, max_time=10.0, honor_time=False)
# self.br.set_handle_refresh(True, max_time=10.0)
# self.br.set_handle_refresh(True, honor_time=False)
(self.opts, self.args) = self.parseopts()
def usage(self):
"""Prints out a usage summary and exits."""
print "\n", 78*"#"
print """\n
Usage: mantisctl.py [Options], version = mantisctl.py 0.3
ACTION:
-T, --test Test the script
-L, --list List the Mantis options
-r, --report Report a new bug
-h, --help show this help message and exit
OPTIONS:
--version show program's version number and exit
-n, --noconfirm Do not ask for confirmation before submitting a report
-b BASEURL, --baseurl=BASEURL
Base URL for a Mantis BTS instance [optional if you set
it in the script configuration section]. [default: None]
-u USERNAME, --username=USERNAME
User name [optional if you set it in the script
itself]. [default: None]
-w PASSWORD, --password=PASSWORD
User password [optional if you set it in the script
itself]. [default: None]
-a ASSIGNTO, --assignto=ASSIGNTO
Assign to [optional]
-c CATEGORY, --category=CATEGORY
Bug category <mandatory>
-y SEVERITY, --severity=SEVERITY
Bug severity <mandatory>
-s SUMMARY, --summary=SUMMARY
Bug summary <mandatory>
-d DESCRIPTION, --desc=DESCRIPTION
Bug description [optional]
-p PVERSION, --product=PVERSION
Product version <mandatory>
-t TVERSION, --target=TVERSION
Target version [optional]
"""
sys.exit(1)
def parseopts(self):
"""Parses options and arguments passed to the script
and returns a tuple of a dictionary (opts) and a list (args)."""
from optparse import OptionParser
usage = "%prog [Options], version = %prog 0.3"
op = OptionParser(usage=usage, version='%prog 0.3')
op.add_option("-T", "--test", action="store_true", dest="test", default=False, help="Test the script")
op.add_option("-L", "--list", action="store_true", dest="list", default=False, help="List the Mantis options")
op.add_option("-r", "--report", action="store_true", dest="report", default=False, help="Report a new bug")
op.add_option("-n", "--noconfirm", action="store_true", dest="noconfirm", default=False, help="Do not ask for confirmation before submitting a report")
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].")
op.add_option("-u", "--username", dest="username", help="User name [optional if you set it in the script itself]. [default: %default]")
op.add_option("-w", "--password", dest="password", help="User password [optional if you set it in the script itself]. [default: %default]")
op.add_option("-a", "--assignto", dest="assignto", help="Assign to [optional]")
op.add_option("-c", "--category", dest="category", help="Bug category <mandatory>")
op.add_option("-y", "--severity", dest="severity", help="Bug severity <mandatory>")
op.add_option("-s", "--summary", dest="summary", help="Bug summary <mandatory>")
op.add_option("-d", "--desc", dest="description", help="Bug description [optional]")
op.add_option("-p", "--product", dest="pversion", help="Product version <mandatory>")
op.add_option("-t", "--target", dest="tversion", help="Target version [optional]")
return op.parse_args()
def _getbaseurl(self):
"""Returns the Base URL for a Mantis BTS instance from options or
configuration. Will exit if basurl is not provided either as an option
or in the configuration section."""
if not self.opts.baseurl:
try:
url = baseurl
except:
sys.exit("Base URL is not provided either as an option or in the configuration section.")
else:
url = self.opts.baseurl
return url
def checkbts(self):
"""Checks if the URL for the Mantis BTS instance front page could be
opened and if the title of main page corresponds to the configuration
option 'title'."""
# Get the base url from options or configuration section
url = self._getbaseurl()
try:
self.br.open(url)
mainpagetitle = self.br.title()
except:
print "Cannot open a Mantis BTS instance main page."
sys.exit(1)
# basetitle is defined in the configuration section
if re.search(basetitle, mainpagetitle):
return True
else:
print "This doesn't seem to be a Mantis BTS instance main page."
sys.exit(1)
def askconfirm(self):
"""Asks for a confirmation to submit a bug report. Exits without
submitting a bug report if 'y' is not provided as an answer."""
msg = "\nYou are about to submit a bug report with the following data:\n"
self.printdata(msg)
yes = ['y', 'Y']
var = raw_input("Press 'y' to submit the report or any other key to abort and hit ENTER: ")
print var
if var in yes:
print "Submitting your bug report..."
else:
print "Exiting without submitting the report..."
sys.exit(0)
def _printwarning(self, msg, list):
print "\nYou must specify a %s from the following list: \n" % (msg)
print list
sys.exit(1)
def printdata(self, msg):
"""Prints the bug report data either for verification before and after
submission."""
print msg
print "\tCategory: \t\t%s" % self.category
print "\tSeverity: \t\t%s" % self.opts.severity
print "\tAssigned To: \t\t%s" % self.username
print "\tProduct Version: \t%s" % self.pversion
print "\tTarget Version: \t%s" % self.tversion
print "\tSummary: \t\t%s" % self.summary
print "\tDescription: \t\t%s" % self.description
def _checkcredentials(self):
if self.opts.username:
self.username = self.opts.username
elif username:
self.username = username
else:
print "\nUsername is not specified either in the script configuration or on the command line."
self._usage()
if self.opts.password:
self.password = self.opts.password
elif password:
self.password = password
else:
print "\nPassword is not specified either in the script or on the command line."
self._usage()
def login(self):
"""Performs logging into a Mantis BTS instance as an authenticated
reporter."""
# Check if we have the credentials to login into a Mantis BTS instance.
self._checkcredentials()
self.loginlink = self.br.links(text_regex=re.compile("Login")).next()
self.br.follow_link(self.loginlink)
self.br.select_form(name="login_form")
self.br.form["username"] = self.username
self.br.form["password"] = self.password
self.br.submit()
def _gethandlers(self, response):
"""Returns a dictionary of 'Assign To' names and ids directly from the bug report page."""
response.seek(0)
handlersoup = BeautifulSoup(response)
selects = handlersoup.findAll('select', { "name" : "handler_id" })[0].findAll("option")
handlers = {}
for select in selects:
handler_id = select.attrs[0][1].strip().encode('utf8')
if len (select.contents) > 0:
handler_login = select.contents[0].strip().encode('utf8')
else:
handler_login = ""
handlers[handler_login] = handler_id
return handlers
def _getseverities(self, response):
"""Returns a dictionary of 'Severity' names and ids directly from the bug report page."""
response.seek(0)
severitysoup = BeautifulSoup(response)
selects = severitysoup.findAll('select', { "name" : "severity" })[0].findAll("option")
severities = {}
for select in selects:
severity_id = select.attrs[0][1].strip().encode('utf8')
if len (select.contents) > 0:
severity_name = select.contents[0].strip().encode('utf8')
else:
severity_name = ""
severities[severity_name] = severity_id
return severities
def _getcategories(self):
"""Returns a list of bug categories directly from the bug reporting page."""
category_control = self.br.form.find_control("category")
category_list = []
for i in category_control.items:
category_list.append(i.name)
return category_list
def _getpversions(self):
"""Returns a list of product versions directly from the bug reporting page."""
pversion_control = self.br.form.find_control("product_version")
pversion_list = []
for i in pversion_control.items:
pversion_list.append(i.name)
return pversion_list
def _gettversions(self):
"""Returns a list of target versions directly from the bug reporting page."""
tversion_control = self.br.form.find_control("target_version")
tversion_list = []
for i in tversion_control.items:
tversion_list.append(i.name)
return tversion_list
def setreportdata(self):
"""Handles filling out the bug report form."""
reportlink = self.br.links(text_regex=re.compile("Report Issue")).next()
response = self.br.follow_link(reportlink)
self.handlers = self._gethandlers(response)
self.severities = self._getseverities(response)
self.br.select_form(name="report_bug_form")
self.categories = self._getcategories()
self.pversions = self._getpversions()
self.tversions = self._gettversions()
def checkreportopts(self):
"""Verifies the validity of options passed to the script."""
# summary
if not self.opts.summary:
sys.exit("You must provide a bug summary!")
else:
self.summary = self.opts.summary
# description
if self.opts.description and len(self.opts.description.strip()) > 1:
self.description = self.opts.description
else:
self.description = self.opts.summary
# handler
if self.opts.assignto and len(self.opts.assignto) > 0:
if self.opts.assignto not in self.handlers.keys():
self._printwarning("developer to assign bug to", self.handlers.keys())
else:
self.handler = str(self.handlers[self.opts.assignto])
else:
self.handler = '0'
# category
if not self.opts.category:
self._printwarning("category", self.categories)
else:
if self.opts.category in self.categories:
self.category = self.opts.category
else:
self._printwarning("category", self.categories)
# severity
if self.opts.severity and len(self.opts.severity) > 0:
if self.opts.severity in self.severities.keys():
self.severity = str(self.severities[self.opts.severity])
else:
self._printwarning("severity", self.severities.keys())
# product version
if not self.opts.pversion:
self._printwarning("product version", self.pversions)
else:
if self.opts.pversion in self.pversions:
self.pversion = self.opts.pversion
else:
self._printwarning("product version", self.pversions)
# target version
if not self.opts.tversion:
self._printwarning("target version", self.tversions)
else:
if self.opts.tversion in self.tversions:
self.tversion = self.opts.tversion
else:
self._printwarning("target version", self.tversions)
def filloutreportform(self):
"""Fills out required controls on the bug reporting form."""
self.br.form["category"] = [self.category]
self.br.form["severity"] = [self.severity]
self.br.form["handler_id"] = [self.handler]
self.br.form["product_version"] = [self.pversion]
if self.tversion:
self.br.form["target_version"] = [self.tversion]
self.br.form["summary"] = self.summary
self.br.form["description"] = self.description
def reportbug(self):
"""Handles submission of a bug report."""
self.setreportdata()
self.checkreportopts()
# depends on self.checkreportopts as it sets all self.var(s)
self.filloutreportform()
if not self.opts.noconfirm:
self.askconfirm()
try:
#TODO
response = self.br.submit()
# response.seek(0)
# reportsoup = BeautifulSoup(response)
# print reportsoup
except:
pass
# import sys, logging
# logger = logging.getLogger("mechanize")
# logger.addHandler(logging.StreamHandler(sys.stdout))
# logger.setLevel(logging.DEBUG)
self.br.close()
msg = "\nBug report submission appears to be successful! The following data have been submitted:\n"
self.printdata(msg)
sys.exit(0)
def listopts(self):
"""Lists options passed to the script and those obtained directly from
a Mantis BTS instance."""
self.setreportdata()
self.checkreportopts()
print "Severities: \n", "\t", self.severities.keys(), "\n"
print "Categories: \n", "\t", self.categories, "\n"
print "Developers: \n", "\t", self.handlers.keys(), "\n"
print "Product Versions: \n", "\t", self.pversions, "\n"
def runchoice(self):
"""Process a run choice such as list options, test the script, show
help, or submit a bug report or print usage summary and exit if no
action has been specified."""
if self.opts.list:
print "Listing the Mantis options...\n"
self.listopts()
elif self.opts.test:
print "\nTesting the script...\n",
self.setreportdata()
self.checkreportopts()
print "\nTest run completed without errors.\n"
sys.exit(0)
elif self.opts.report:
print "\nSubmitting a new bug report.\n"
self.reportbug()
else:
print """\nNo action such as -r (submit a bug report), -L (list
options), or -T (test run) has been specified. Exiting...\n"""
self._usage()
def dorun(self):
"""Main controller of the script. It will be called if the script is
run from a command line or can be run by an external script. No other
interface is available."""
# Check if we can access the main page of a Mantis BTS instance.
self.checkbts()
# Login into a Mantis BTS instance.
self.login()
# Select and run an action.
self.runchoice()
if __name__=='__main__':
"""Check if the script is run from a command line, initialize the main
class and either show usage summary or run the main controller method."""
if len(sys.argv) == 1:
sys.argv.append("--help")
browser = Mantisctl()
else:
browser = Mantisctl()
browser.dorun()
# vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: