18,91 → 18,50 |
# You should have received a copy of the GNU General Public License |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
### |
# Changelog: |
# Version: |
# 0.1: Initial release - bug reporting to Mantis is working. |
# 0.2: |
# - Added a confirmation message and an option to override it. |
# - Fixed a bug in the option parsing method that would not correctly |
# return options when script is run with no arguments. |
### |
|
import re, sys |
import mechanize |
import logging |
import re, sys, logging |
from mechanize import Browser |
from mechanize import CookieJar |
from mechanize import LWPCookieJar |
from BeautifulSoup import BeautifulSoup |
|
############################################################################## |
# CONFIGURATION |
# USER CONFIGURATION - edit for your site if necessary. # |
############################################################################## |
# USER CONFIGURATION - edit for your site. |
#username = "xxxxxxxx" |
#password = "xxxxxxxx" |
baseurl = "http://bugs.scribus.net/main_page.php" |
title = "Mantis Issue Tracker for Scribus" |
filename = "mantisctl.cookies" |
handlers = { '' : 0, 'ale' : 20, 'alexandre' : 362, 'avox' : 569, 'cbradney' : 2, |
'christoph_s' : 387, 'deejay1' : 12, 'fschmid' : 3, 'h_a_j_s' : 525, |
'Herm' : 1552, 'jghali' : 649, 'jo-hannes' : 114, 'ltning' : 651, |
'malex' : 73, 'mhanski' : 72, 'nimda' : 6, 'pierremarchand' : 1260, |
'plinnell' : 4, 'psmedley' : 1408, 'rajeev_jsv' : 665, 'ringerc' : 212, |
'sjc' : 28, 'subik' : 8, 'Tsoots' : 22 } |
basetitle = "^Mantis Issue Tracker" |
#baseurl = None |
#username = None |
#password = None |
############################################################################## |
# SYSTEM CONFIGURATION - you do not usually need to edit this. |
severities = { 'feature' : '10', 'trivial': '20', 'text' : '30', 'tweak' : '40', 'minor' |
: '50', 'major' : '60', 'crash' : '70', 'block' : '80'} |
############################################################################## |
|
class Mantisctl: |
"""This class handles Mantis Bug Tracking system data retrieval, bug |
report submission, and control tasks.""" |
"""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_refresh(True, honor_time=False) |
self.br.set_handle_refresh(True, max_time=30.0, honor_time=False) |
self.br.set_handle_referer(True) |
(self.opts, self.args) = self._getopts() |
assert self._checkbts() |
(self.opts, self.args) = self.parseopts() |
|
def _askconfirm(self): |
print "\nYou will submit a bug report with the following data:\n" |
self._printdata() |
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 _checkbts(self): |
""" Checks if the URL for the front page could be opened.""" |
try: |
self.br.open(self.baseurl) |
if self.br.title() == self.title: |
return True |
else: |
print "Main page does not correspond to the configuration." |
sys.exit(1) |
except: |
print "Cannot open the chosen Mantis Bug Tracker main page." |
sys.exit(1) |
|
def _usage(self): |
def usage(self): |
"""Prints out a usage summary and exits.""" |
print "\n", 78*"#" |
print """\n |
Usage: mantisctl.py [Options], version = mantisctl.py 0.2 |
Usage: mantisctl.py [Options], version = mantisctl.py 0.3 |
|
Options: |
--version show program's version number and exit |
-h, --help show this help message and exit |
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] |
126,34 → 85,19 |
""" |
sys.exit(1) |
|
def _getopts(self): |
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 |
"""This procedure parses options and arguments passed to the program |
and stores them in a dictionary (opts) and a list (args)""" |
try: |
self.title = title |
except: |
sys.exit("You must set the title of the base Mantis BTS page in the configuration section of this script.") |
try: |
self.baseurl = baseurl |
except: |
sys.exit("You must set base URL for Mantis BTS in the configuration section of this script.") |
try: |
self.username = username |
except: |
self.username = "None" |
try: |
self.password = password |
except: |
self.password = "None" |
usage = "%prog [Options], version = %prog 0.2" |
op = OptionParser(usage=usage, version='%prog 0.2') |
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("-u", "--username", dest="username", default=self.username, help="User name [optional if you set it in the script itself]. [default: %default]") |
op.add_option("-w", "--password", dest="password", default=self.password, help="User password [optional if you set it in the script itself]. [default: %default]") |
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]") |
op.add_option("-u", "--username", dest="username", default=username, help="User name [optional if you set it in the script itself]. [default: %default]") |
op.add_option("-w", "--password", dest="password", default=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>") |
163,189 → 107,286 |
op.add_option("-t", "--target", dest="tversion", help="Target version [optional]") |
return op.parse_args() |
|
def _printcookies(self): |
for i in self.cj: |
print i |
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 confguration section.""" |
if not self.opts.baseurl: |
try: |
url = baseurl |
except: |
sys.exit("Base URL is not provided either as an option or in the confguration section.") |
else: |
url = self.opts.baseurl |
return baseurl |
|
def _setupcookies(self): |
policy = mechanize.DefaultCookiePolicy(rfc2965=True) |
cj = LWPCookieJar(policy=policy) |
self.br.set_cookiejar(cj) |
self.cj = cj |
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 _savecookies(self, filename): |
self.cj.save(filename, ignore_discard = True, ignore_expires = True) |
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 _loadcookies(self, filename): |
self.cj.load(filename, ignore_discard = True, ignore_expires = True) |
def _printwarning(self, msg, list): |
print "\nYou must specify a %s from the following list: \n" % (msg) |
print list |
sys.exit(1) |
|
def _dologin(self): |
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.opts.username |
self.br.form["password"] = self.opts.password |
self.br.form["username"] = self.username |
self.br.form["password"] = self.password |
self.br.submit() |
|
def _login(self): |
self.filename = filename |
try: |
self._loadcookies(self.filename) |
if not self.cj.as_lwp_str(): |
self._dologin() |
except IOError: |
self._dologin() |
self.cj.save(self.filename) |
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 _getmantisopts(self): |
self.handlers = handlers |
self.severities = severities |
self.category_control = self.br.form.find_control("category") |
self.category_list = [] |
for i in self.category_control.items: |
self.category_list.append(i.name) |
self.severity_control = self.br.form.find_control("severity") |
self.severity_list = [] |
for i in self.severity_control.items: |
self.severity_list.append(i.name) |
self.handler_control = self.br.form.find_control("handler_id") |
self.handler_list = [] |
for i in self.handler_control.items: |
self.handler_list.append(int(i.name)) |
self.pversion_control = self.br.form.find_control("product_version") |
self.pversion_list = [] |
for i in self.pversion_control.items: |
self.pversion_list.append(i.name) |
self.tversion_control = self.br.form.find_control("target_version") |
self.tversion_list = [] |
for i in self.tversion_control.items: |
self.tversion_list.append(i.name) |
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 _checkopts(self): |
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!") |
if not self.opts.description: |
self.opts.description = self.opts.summary |
elif len(self.opts.description.strip()) < 1: |
self.opts.description = self.opts.summary |
for i in self.handler_list: |
if i not in self.handlers.values(): |
sys.exit("Developer list at the top of this script is not complete.") |
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(): |
print "You must specify a developer to assign bug to from the following list: \n", self.handlers.keys() |
sys.exit(1) |
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: |
print "You must specify a category from the following list: \n", self.category_list |
sys.exit(1) |
self._printwarning("category", self.categories) |
else: |
if self.opts.category in self.category_list: |
pass |
if self.opts.category in self.categories: |
self.category = self.opts.category |
else: |
print "Your category is not in the list: \n", self.category_list |
sys.exit(1) |
for i in self.severity_list: |
if i not in self.severities.values(): |
sys.exit("Severity list at the top of this script is not complete.") |
if not self.opts.severity: |
print "You must specify a severity from the following list: \n", self.severities.keys() |
sys.exit(1) |
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 = self.severities[self.opts.severity] |
self.severity = str(self.severities[self.opts.severity]) |
else: |
print "Your severity is not in the list: \n", self.severities.keys() |
sys.exit(1) |
self._printwarning("severity", self.severities.keys()) |
# product version |
if not self.opts.pversion: |
print "You must specify a product version from the following list: \n", self.pversion_list |
sys.exit(1) |
self._printwarning("product version", self.pversions) |
else: |
if self.opts.pversion in self.pversion_list: |
pass |
if self.opts.pversion in self.pversions: |
self.pversion = self.opts.pversion |
else: |
print "Your product version is not in the list: \n", self.pversion_list |
sys.exit(1) |
if self.opts.tversion: |
if self.opts.tversion in self.tversion_list: |
pass |
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: |
print "Your target version is not in the list: \n", self.tversion_list |
sys.exit(1) |
self._printwarning("target version", self.tversions) |
|
def _setupreportform(self): |
self._getmantisopts() |
if self.opts.list: |
print "Severities: \n", "\t", self.severities.keys(), "\n" |
print "Categories: \n", "\t", self.category_list, "\n" |
print "Developers: \n", "\t", self.handlers.keys(), "\n" |
print "Product Versions: \n", "\t", self.pversion_list, "\n" |
sys.exit(0) |
self._checkopts() |
self.br.form["category"] = [self.opts.category] |
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.opts.pversion] |
if self.opts.tversion: |
self.br.form["target_version"] = [self.opts.tversion] |
self.br.form["summary"] = self.opts.summary |
self.br.form["description"] = self.opts.description |
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): |
reportlink = self.br.links(text_regex=re.compile("Report Issue")).next() |
self.br.follow_link(reportlink) |
self.br.select_form(name="report_bug_form") |
self._setupreportform() |
|
def _printdata(self): |
print "\tCategory: \t\t%s" % self.opts.category |
print "\tSeverity: \t\t%s" % self.opts.severity |
print "\tAssigned To: \t\t%s" % self.opts.assignto |
print "\tProduct Version: \t%s" % self.opts.pversion |
print "\tTarget Version: \t%s" % self.opts.tversion |
print "\tSummary: \t\t%s" % self.opts.summary |
print "\tDescription: \t\t%s" % self.opts.description |
|
def doreportbug(self): |
self.reportbug() |
"""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() |
self.askconfirm() |
try: |
self.br.submit() |
except: |
pass |
self.br.close() |
print "\nBug report submission appears to be successful! The following data have been submitted:\n" |
self._printdata() |
msg = "\nBug report submission appears to be successful! The following data have been submitted:\n" |
self.printdata(msg) |
sys.exit(0) |
|
def _checkcredentials(self): |
if self.opts.username == 'None': |
print "\nUsername is not specified either in the script configuration or on the command line." |
self._usage() |
if self.opts.password == 'None': |
print "\nPassword is not specified either in the script or on the command line." |
self._usage() |
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 dorun(self): |
self._checkcredentials() |
self._setupcookies() |
self._login() |
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() |
if self.opts.test: |
print "Testing the script...", |
self.reportbug() |
print "everything seems to be fine." |
sys.exit(0) |
if self.opts.report: |
self.doreportbug() |
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() |