diff --git a/bin/bugz b/bin/bugz
index 39229ac..afaa4f8 100755
--- a/bin/bugz
+++ b/bin/bugz
@@ -4,79 +4,28 @@ import locale
import sys
import traceback
-from optparse import OptionParser
-
-from bugz import cli, __version__
from bugz.cli import BugzError, PrettyBugz
+from bugz.clparser import make_parser, get_kwds
def main():
- if len(sys.argv) < 2:
- PrettyBugz.help()
- sys.exit(-1)
-
- cmd = sys.argv[1]
-
- parser = OptionParser(version = '%%prog %s' % __version__)
-
- global_opts = PrettyBugz.options
- for x in global_opts.values():
- parser.add_option(x)
-
- if cmd == '--help' or cmd == '-h' or cmd == 'help':
- PrettyBugz.help()
- sys.exit(0)
- elif cmd == '--version' :
- parser.print_version()
- sys.exit(0)
-
- cmd_func = getattr(PrettyBugz, cmd, None)
- if cmd_func is None:
- print '!! Error: Unable to recognise command: %s' % cmd
- print
- PrettyBugz.help()
- sys.exit(-1)
-
- cmd_options = getattr(cmd_func, "options", {})
- cmd_args = getattr(cmd_func, "args", "[options]")
- parser.set_description(cmd_func.__doc__)
- parser.set_usage('%%prog %s %s' % (cmd, cmd_args))
-
- for x in cmd_options.values():
- parser.add_option(x)
+ parser = make_parser()
# parse options
- opts, args = parser.parse_args(sys.argv[2:])
-
- # separate bugz options and cmd options
- bugz_kwds = {}
- for name, opt in global_opts.items():
- try:
- bugz_kwds[name] = getattr(opts, name)
- except AttributeError:
- pass
-
- cmd_kwds = {}
- for name, opt in cmd_options.items():
- try:
- cmd_kwds[name] = getattr(opts, name)
- except AttributeError:
- pass
+ args = parser.parse_args()
+ (bugz_kwds, cmd_kwds) = get_kwds(args)
try:
bugz = PrettyBugz(**bugz_kwds)
- getattr(bugz, cmd)(*args, **cmd_kwds)
+ args.func(bugz, **cmd_kwds)
except BugzError, e:
print ' ! Error: %s' % e
- print
- parser.print_help()
sys.exit(-1)
except TypeError, e:
print ' ! Error: Incorrect number of arguments supplied'
print
traceback.print_exc()
- parser.print_help()
sys.exit(-1)
except RuntimeError, e:
diff --git a/bugz/bugzilla.py b/bugz/bugzilla.py
index 7e1a2a9..2134de6 100644
--- a/bugz/bugzilla.py
+++ b/bugz/bugzilla.py
@@ -7,6 +7,7 @@ import locale
import mimetypes
import os
import re
+import sys
from cookielib import LWPCookieJar, CookieJar
from cStringIO import StringIO
@@ -84,6 +85,8 @@ def get_content_type(filename):
#
# Override the behaviour of elementtree and allow us to
# force the encoding to utf-8
+# Not needed in Python 2.7, since ElementTree.XMLTreeBuilder uses the forced
+# encoding.
#
class ForcedEncodingXMLTreeBuilder(ElementTree.XMLTreeBuilder):
@@ -235,7 +238,7 @@ class Bugz:
base64string = base64.encodestring('%s:%s' % (self.httpuser, self.httppassword))[:-1]
req.add_header("Authorization", "Basic %s" % base64string)
resp = self.opener.open(req)
- re_request_login = re.compile(r'
.*Log in to Bugzilla')
+ re_request_login = re.compile(r'.*Log in to .*')
if not re_request_login.search(resp.read()):
self.log('Already logged in.')
self.authenticated = True
@@ -255,6 +258,8 @@ class Bugz:
qparams = config.params['auth'].copy()
qparams['Bugzilla_login'] = self.user
qparams['Bugzilla_password'] = self.password
+ if not self.forget:
+ qparams['Bugzilla_remember'] = 'on'
req_url = urljoin(self.base, config.urls['auth'])
req = Request(req_url, urlencode(qparams), config.headers)
@@ -285,7 +290,7 @@ class Bugz:
self.log('Unknown field: ' + field)
columns.append(field)
for row in rows[1:]:
- if row[0].find("Missing Search") != -1:
+ if "Missing Search" in row[0]:
self.log('Bugzilla error (Missing search found)')
return None
fields = {}
@@ -439,9 +444,25 @@ class Bugz:
req.add_header("Authorization", "Basic %s" % base64string)
resp = self.opener.open(req)
- fd = StringIO(resp.read())
+ data = resp.read()
+ # Get rid of control characters.
+ data = re.sub('[\x00-\x08\x0e-\x1f\x0b\x0c]', '', data)
+ fd = StringIO(data)
+
# workaround for ill-defined XML templates in bugzilla 2.20.2
- parser = ForcedEncodingXMLTreeBuilder(encoding = 'utf-8')
+ (major_version, minor_version) = \
+ (sys.version_info[0], sys.version_info[1])
+ if major_version > 2 or \
+ (major_version == 2 and minor_version >= 7):
+ # If this is 2.7 or greater, then XMLTreeBuilder
+ # does what we want.
+ parser_class = ElementTree.XMLParser
+ else:
+ # Running under Python 2.6, so we need to use our
+ # subclass of XMLTreeBuilder instead.
+ parser_class = ForcedEncodingXMLTreeBuilder
+ parser = parser_class(encoding = 'utf-8')
+
etree = ElementTree.parse(fd, parser)
bug = etree.find('.//bug')
if bug and bug.attrib.has_key('error'):
@@ -517,7 +538,7 @@ class Bugz:
FIELDS = ('bug_file_loc', 'bug_severity', 'short_desc', 'bug_status',
'status_whiteboard', 'keywords',
'op_sys', 'priority', 'version', 'target_milestone',
- 'assigned_to', 'rep_platform', 'product', 'component')
+ 'assigned_to', 'rep_platform', 'product', 'component', 'token')
FIELDS_MULTI = ('blocked', 'dependson')
@@ -637,6 +658,11 @@ class Bugz:
try:
resp = self.opener.open(req)
+ re_error = re.compile(r'id="error_msg".*>([^<]+)<')
+ error = re_error.search(resp.read())
+ if error:
+ print error.group(1)
+ return []
return modified
except:
return []
@@ -753,7 +779,7 @@ class Bugz:
return 0
def attach(self, bugid, title, description, filename,
- content_type = 'text/plain'):
+ content_type = 'text/plain', ispatch = False):
"""Attach a file to a bug.
@param bugid: bug id
@@ -777,7 +803,11 @@ class Bugz:
qparams['bugid'] = bugid
qparams['description'] = title
qparams['comment'] = description
- qparams['contenttypeentry'] = content_type
+ if ispatch:
+ qparams['ispatch'] = '1'
+ qparams['contenttypeentry'] = 'text/plain'
+ else:
+ qparams['contenttypeentry'] = content_type
filedata = [('data', filename, open(filename).read())]
content_type, body = encode_multipart_formdata(qparams.items(),
diff --git a/bugz/cli.py b/bugz/cli.py
index e97da1b..8f25450 100644
--- a/bugz/cli.py
+++ b/bugz/cli.py
@@ -8,8 +8,6 @@ import sys
import tempfile
import textwrap
-from optparse import OptionParser
-from optparse import make_option
from urlparse import urljoin
try:
@@ -122,43 +120,7 @@ def block_edit(comment, comment_from = ''):
class BugzError(Exception):
pass
-#
-# ugly optparse callbacks (really need to integrate this somehow)
-#
-
-def modify_opt_fixed(opt, opt_str, val, parser):
- parser.values.status = 'RESOLVED'
- parser.values.resolution = 'FIXED'
-
-def modify_opt_invalid(opt, opt_str, val, parser):
- parser.values.status = 'RESOLVED'
- parser.values.resolution = 'INVALID'
-
class PrettyBugz(Bugz):
- options = {
- 'base': make_option('-b', '--base', type='string',
- default = 'https://bugs.gentoo.org/',
- help = 'Base URL of Bugzilla'),
- 'user': make_option('-u', '--user', type='string',
- help = 'Username for commands requiring authentication'),
- 'password': make_option('-p', '--password', type='string',
- help = 'Password for commands requiring authentication'),
- 'httpuser': make_option('-H', '--httpuser', type='string',
- help = 'Username for basic http auth'),
- 'httppassword': make_option('-P', '--httppassword', type='string',
- help = 'Password for basic http auth'),
- 'forget': make_option('-f', '--forget', action='store_true',
- help = 'Forget login after execution'),
- 'columns': make_option('--columns', type='int', default = 0,
- help = 'Maximum number of columns output should use'),
- 'encoding': make_option('--encoding',
- help = 'Output encoding (default: utf-8).'),
- 'skip_auth': make_option('--skip-auth', action='store_true',
- default = False, help = 'Skip Authentication.'),
- 'quiet': make_option('-q', '--quiet', action='store_true',
- default = False, help = 'Quiet mode'),
- }
-
def __init__(self, base, user = None, password =None, forget = False,
columns = 0, encoding = '', skip_auth = False,
quiet = False, httpuser = None, httppassword = None ):
@@ -195,10 +157,11 @@ class PrettyBugz(Bugz):
def get_input(self, prompt):
return raw_input(prompt)
- def search(self, *args, **kwds):
+ def search(self, **kwds):
"""Performs a search on the bugzilla database with the keywords given on the title (or the body if specified).
"""
- search_term = ' '.join(args).strip()
+ search_term = ' '.join(kwds['terms']).strip()
+ del kwds['terms']
show_status = kwds['show_status']
del kwds['show_status']
show_url = kwds['show_url']
@@ -232,41 +195,6 @@ class PrettyBugz(Bugz):
self.listbugs(result, show_url, show_status)
- search.args = " [options..]"
- search.options = {
- 'order': make_option('-o', '--order', type='choice',
- choices = config.choices['order'].keys(),
- default = 'number'),
- 'assigned_to': make_option('-a', '--assigned-to',
- help = 'email the bug is assigned to'),
- 'reporter': make_option('-r', '--reporter',
- help = 'email the bug was reported by'),
- 'cc': make_option('--cc',help = 'Restrict by CC email address'),
- 'commenter': make_option('--commenter',help = 'email that commented the bug'),
- 'status': make_option('-s', '--status', action='append',
- help = 'Bug status (for multiple choices,'
- 'use --status=NEW --status=ASSIGNED) or --status=all for all statuses'),
- 'severity': make_option('--severity', action='append',
- choices = config.choices['severity'],
- help = 'Restrict by severity.'),
- 'priority': make_option('--priority', action='append',
- choices = config.choices['priority'].values(),
- help = 'Restrict by priority (1 or more)'),
- 'comments': make_option('-c', '--comments', action='store_true',
- help = 'Search comments instead of title'),
- 'product': make_option('--product', action='append',
- help = 'Restrict by product (1 or more)'),
- 'component': make_option('-C', '--component', action='append',
- help = 'Restrict by component (1 or more)'),
- 'keywords': make_option('-k', '--keywords', help = 'Bug keywords'),
- 'whiteboard': make_option('-w', '--whiteboard',
- help = 'Status whiteboard'),
- 'show_status': make_option('--show-status', help='show status of bugs',
- action = 'store_true', default = False),
- 'show_url': make_option('--show-url', help='Show bug id as a url.',
- action = 'store_true', default = False),
- }
-
def namedcmd(self, command, show_status=False, show_url=False):
"""Run a command stored in Bugzilla by name."""
log_msg = 'Running namedcmd \'%s\''%command
@@ -280,14 +208,6 @@ class PrettyBugz(Bugz):
self.listbugs(result, show_url, show_status)
- namedcmd.args = ""
- namedcmd.options = {
- 'show_status': make_option('--show-status', help='show status of bugs',
- action = 'store_true', default = False),
- 'show_url': make_option('--show-url', help='Show bug id as a url.',
- action = 'store_true', default = False),
- }
-
def get(self, bugid, comments = True, attachments = True):
""" Fetch bug details given the bug id """
self.log('Getting bug %s ..' % bugid)
@@ -384,13 +304,6 @@ class PrettyBugz(Bugz):
i += 1
print
- get.args = " [options..]"
- get.options = {
- 'comments': make_option("-n", "--no-comments", dest = 'comments',
- action="store_false", default = True,
- help = 'Do not show comments'),
- }
-
def post(self, product = None, component = None,
title = None, description = None, assigned_to = None,
cc = None, url = None, keywords = None,
@@ -579,42 +492,6 @@ class PrettyBugz(Bugz):
else:
raise RuntimeError('Failed to submit bug')
- post.args = "[options]"
- post.options = {
- 'product': make_option('--product', help = 'Product'),
- 'component': make_option('--component', help = 'Component'),
- 'prodversion': make_option('--prodversion',
- help = 'Version of the product'),
- 'title': make_option('-t', '--title', help = 'Title of bug'),
- 'description': make_option('-d', '--description',
- help = 'Description of the bug'),
- 'description_from': make_option('-F' , '--description-from',
- help = 'Description from contents of'
- ' file'),
- 'append_command': make_option('--append-command',
- help = 'Append the output of a command to the description.'),
- 'assigned_to': make_option('-a', '--assigned-to',
- help = 'Assign bug to someone other than '
- 'the default assignee'),
- 'cc': make_option('--cc', help = 'Add a list of emails to CC list'),
- 'url': make_option('-U', '--url',
- help = 'URL associated with the bug'),
- 'dependson': make_option('--depends-on', dest='dependson', help = 'Add a list of bug dependencies'),
- 'blocked': make_option('--blocked', help = 'Add a list of blocker bugs'),
- 'keywords': make_option('-k', '--keywords', help = 'List of bugzilla keywords'),
- 'batch': make_option('--batch', action="store_true",
- help = 'do not prompt for any values'),
- 'default_confirm': make_option('--default-confirm',
- choices = ['y','Y','n','N'],
- default = 'y',
- help = 'default answer to confirmation question (y/n)'),
- 'priority': make_option('--priority',
- choices=config.choices['priority'].values()),
- 'severity': make_option('-S', '--severity',
- choices=config.choices['severity']),
- }
-
-
def modify(self, bugid, **kwds):
"""Modify an existing bug (eg. adding a comment or changing resolution.)"""
if 'comment_from' in kwds:
@@ -637,6 +514,15 @@ class PrettyBugz(Bugz):
kwds['comment'] = block_edit('Enter comment:')
del kwds['comment_editor']
+ if kwds['fixed']:
+ kwds['STATUS'] = 'CLOSED'
+ kwds['resolution'] = 'FIXED'
+ del kwds['fixed']
+
+ if kwds['invalid']:
+ kwds['STATUS'] = 'CLOSED'
+ kwds['resolution'] = 'INVALID'
+ del kwds['invalid']
result = Bugz.modify(self, bugid, **kwds)
if not result:
raise RuntimeError('Failed to modify bug')
@@ -645,53 +531,6 @@ class PrettyBugz(Bugz):
for field, value in result:
self.log(' %-12s: %s' % (field, value))
-
- modify.args = " [options..]"
- modify.options = {
- 'title': make_option('-t', '--title', help = 'Set title of bug'),
- 'comment_from': make_option('-F', '--comment-from',
- help = 'Add comment from file. If -C is also specified, the editor will be opened with this file as its contents.'),
- 'comment_editor': make_option('-C', '--comment-editor',
- action='store_true', default = False,
- help = 'Add comment via default editor'),
- 'comment': make_option('-c', '--comment', help = 'Add comment to bug'),
- 'url': make_option('-U', '--url', help = 'Set URL field of bug'),
- 'status': make_option('-s', '--status',
- choices=config.choices['status'].values(),
- help = 'Set new status of bug (eg. RESOLVED)'),
- 'resolution': make_option('-r', '--resolution',
- choices=config.choices['resolution'].values(),
- help = 'Set new resolution (only if status = RESOLVED)'),
- 'assigned_to': make_option('-a', '--assigned-to'),
- 'duplicate': make_option('-d', '--duplicate', type='int', default=0),
- 'priority': make_option('--priority',
- choices=config.choices['priority'].values()),
- 'severity': make_option('-S', '--severity',
- choices=config.choices['severity']),
- 'fixed': make_option('--fixed', action='callback',
- callback = modify_opt_fixed,
- help = "Mark bug as RESOLVED, FIXED"),
- 'invalid': make_option('--invalid', action='callback',
- callback = modify_opt_invalid,
- help = "Mark bug as RESOLVED, INVALID"),
- 'add_cc': make_option('--add-cc', action = 'append',
- help = 'Add an email to the CC list'),
- 'remove_cc': make_option('--remove-cc', action = 'append',
- help = 'Remove an email from the CC list'),
- 'add_dependson': make_option('--add-dependson', action = 'append',
- help = 'Add a bug to the depends list'),
- 'remove_dependson': make_option('--remove-dependson', action = 'append',
- help = 'Remove a bug from the depends list'),
- 'add_blocked': make_option('--add-blocked', action = 'append',
- help = 'Add a bug to the blocked list'),
- 'remove_blocked': make_option('--remove-blocked', action = 'append',
- help = 'Remove a bug from the blocked list'),
- 'whiteboard': make_option('-w', '--whiteboard',
- help = 'Set Status whiteboard'),
- 'keywords': make_option('-k', '--keywords',
- help = 'Set bug keywords'),
- }
-
def attachment(self, attachid, view = False):
""" Download or view an attachment given the id."""
self.log('Getting attachment %s' % attachid)
@@ -713,30 +552,14 @@ class PrettyBugz(Bugz):
open(safe_filename, 'wb').write(result['fd'].read())
- attachment.args = " [-v]"
- attachment.options = {
- 'view': make_option('-v', '--view', action="store_true",
- default = False,
- help = "Print attachment rather than save")
- }
-
- def attach(self, bugid, filename, content_type = 'text/plain', description = None):
+ def attach(self, bugid, filename, content_type = 'text/plain', patch = False, description = None):
""" Attach a file to a bug given a filename. """
if not os.path.exists(filename):
raise BugzError('File not found: %s' % filename)
if not description:
description = block_edit('Enter description (optional)')
result = Bugz.attach(self, bugid, filename, description, filename,
- content_type)
-
- attach.args = " [-c=] [-d=]"
- attach.options = {
- 'content_type': make_option('-c', '--content-type',
- default='text/plain',
- help = 'Mimetype of the file (default: text/plain)'),
- 'description': make_option('-d', '--description',
- help = 'A description of the attachment.')
- }
+ content_type, patch)
def listbugs(self, buglist, show_url=False, show_status=False):
for row in buglist:
@@ -760,27 +583,3 @@ class PrettyBugz(Bugz):
print line[:self.columns]
self.log("%i bug(s) found." % len(buglist))
-
- @classmethod
- def help(self):
- print 'Usage: bugz [parameter(s)] [options..]'
- print
- print 'Subcommands:'
- print ' search Search for bugs in bugzilla'
- print ' get Get a bug from bugzilla'
- print ' attachment Get an attachment from bugzilla'
- print ' post Post a new bug into bugzilla'
- print ' modify Modify a bug (eg. post a comment)'
- print ' attach Attach file to a bug'
- print ' namedcmd Run a stored search,'
- print
- print 'Examples:'
- print ' bugz get 12345'
- print ' bugz search python --assigned-to liquidx@gentoo.org'
- print ' bugz attachment 5000 --view'
- print ' bugz attach 140574 python-2.4.3.ebuild'
- print ' bugz modify 140574 -c "Me too"'
- print ' bugz namedcmd "Amd64 stable"'
- print
- print 'For more information on subcommands, run:'
- print ' bugz --help'
diff --git a/bugz/clparser.py b/bugz/clparser.py
new file mode 100644
index 0000000..f9bdca9
--- /dev/null
+++ b/bugz/clparser.py
@@ -0,0 +1,283 @@
+#!/usr/bin/env python
+
+import argparse
+
+from bugz import __version__
+from bugz.cli import PrettyBugz
+from bugz.config import config
+
+def make_attach_parser(subparsers):
+ attach_parser = subparsers.add_parser('attach',
+ help = 'attach file to a bug')
+ attach_parser.add_argument('bugid',
+ help = 'the ID of the bug where the file should be attached')
+ attach_parser.add_argument('filename',
+ help = 'the name of the file to attach')
+ attach_parser.add_argument('-c', '--content-type',
+ default='text/plain',
+ help = 'mimetype of the file (default: text/plain)')
+ attach_parser.add_argument('-d', '--description',
+ help = 'a description of the attachment.')
+ attach_parser.add_argument('-p', '--patch',
+ action='store_true',
+ help = 'attachment is a patch')
+ attach_parser.set_defaults(func = PrettyBugz.attach)
+
+def make_attachment_parser(subparsers):
+ attachment_parser = subparsers.add_parser('attachment',
+ help = 'get an attachment from bugzilla')
+ attachment_parser.add_argument('attachid',
+ help = 'the ID of the attachment')
+ attachment_parser.add_argument('-v', '--view',
+ action="store_true",
+ default = False,
+ help = 'print attachment rather than save')
+ attachment_parser.set_defaults(func = PrettyBugz.attachment)
+
+def make_get_parser(subparsers):
+ get_parser = subparsers.add_parser('get',
+ help = 'get a bug from bugzilla')
+ get_parser.add_argument('bugid',
+ help = 'the ID of the bug to retrieve.')
+ get_parser.add_argument("-a", "--no-attachments",
+ action="store_false",
+ default = True,
+ help = 'do not show attachments',
+ dest = 'attachments')
+ get_parser.add_argument("-n", "--no-comments",
+ action="store_false",
+ default = True,
+ help = 'do not show comments',
+ dest = 'comments')
+ get_parser.set_defaults(func = PrettyBugz.get)
+
+def make_modify_parser(subparsers):
+ modify_parser = subparsers.add_parser('modify',
+ help = 'modify a bug (eg. post a comment)')
+ modify_parser.add_argument('bugid',
+ help = 'the ID of the bug to modify')
+ modify_parser.add_argument('-a', '--assigned-to',
+ help = 'change assignee for this bug')
+ modify_parser.add_argument('-C', '--comment-editor',
+ action='store_true',
+ help = 'add comment via default editor')
+ modify_parser.add_argument('-F', '--comment-from',
+ help = 'add comment from file. If -C is also specified, the editor will be opened with this file as its contents.')
+ modify_parser.add_argument('-c', '--comment',
+ help = 'add comment from command line')
+ modify_parser.add_argument('-d', '--duplicate',
+ type = int,
+ default = 0,
+ help = 'this bug is a duplicate')
+ modify_parser.add_argument('-k', '--keywords',
+ help = 'set bug keywords'),
+ modify_parser.add_argument('--priority',
+ choices=config.choices['priority'].values(),
+ help = 'change the priority for this bug')
+ modify_parser.add_argument('-r', '--resolution',
+ choices=config.choices['resolution'].values(),
+ help = 'set new resolution (only if status = RESOLVED)')
+ modify_parser.add_argument('-s', '--status',
+ choices=config.choices['status'].values(),
+ help = 'set new status of bug (eg. RESOLVED)')
+ modify_parser.add_argument('-S', '--severity',
+ choices=config.choices['severity'],
+ help = 'set severity for this bug')
+ modify_parser.add_argument('-t', '--title',
+ help = 'set title of bug')
+ modify_parser.add_argument('-U', '--url',
+ help = 'set URL field of bug')
+ modify_parser.add_argument('-w', '--whiteboard',
+ help = 'set Status whiteboard'),
+ modify_parser.add_argument('--add-cc',
+ action = 'append',
+ help = 'add an email to the CC list')
+ modify_parser.add_argument('--remove-cc',
+ action = 'append',
+ help = 'remove an email from the CC list')
+ modify_parser.add_argument('--add-dependson',
+ action = 'append',
+ help = 'add a bug to the depends list')
+ modify_parser.add_argument('--remove-dependson',
+ action = 'append',
+ help = 'remove a bug from the depends list')
+ modify_parser.add_argument('--add-blocked',
+ action = 'append',
+ help = 'add a bug to the blocked list')
+ modify_parser.add_argument('--remove-blocked',
+ action = 'append',
+ help = 'remove a bug from the blocked list')
+ modify_parser.add_argument('--fixed',
+ action='store_true',
+ help = 'mark bug as RESOLVED, FIXED')
+ modify_parser.add_argument('--invalid',
+ action='store_true',
+ help = 'mark bug as RESOLVED, INVALID')
+ modify_parser.set_defaults(func = PrettyBugz.modify)
+
+def make_namedcmd_parser(subparsers):
+ namedcmd_parser = subparsers.add_parser('namedcmd',
+ help = 'run a stored search')
+ namedcmd_parser.add_argument('command',
+ help = 'the name of the stored search')
+ namedcmd_parser.add_argument('--show-status',
+ action = 'store_true',
+ help = 'show status of bugs')
+ namedcmd_parser.add_argument('--show-url',
+ action = 'store_true',
+ help = 'show bug id as a url')
+ namedcmd_parser.set_defaults(func = PrettyBugz.namedcmd)
+
+def make_post_parser(subparsers):
+ post_parser = subparsers.add_parser('post',
+ help = 'post a new bug into bugzilla')
+ post_parser.add_argument('--product',
+ help = 'product')
+ post_parser.add_argument('--component',
+ help = 'component')
+ post_parser.add_argument('--prodversion',
+ help = 'version of the product')
+ post_parser.add_argument('-t', '--title',
+ help = 'title of bug')
+ post_parser.add_argument('-d', '--description',
+ help = 'description of the bug')
+ post_parser.add_argument('-F' , '--description-from',
+ help = 'description from contents of file')
+ post_parser.add_argument('--append-command',
+ help = 'append the output of a command to the description')
+ post_parser.add_argument('-a', '--assigned-to',
+ help = 'assign bug to someone other than the default assignee')
+ post_parser.add_argument('--cc',
+ help = 'add a list of emails to CC list')
+ post_parser.add_argument('-U', '--url',
+ help = 'URL associated with the bug')
+ post_parser.add_argument('--depends-on',
+ help = 'add a list of bug dependencies',
+ dest='dependson')
+ post_parser.add_argument('--blocked',
+ help = 'add a list of blocker bugs')
+ post_parser.add_argument('-k', '--keywords',
+ help = 'list of bugzilla keywords')
+ post_parser.add_argument('--batch',
+ action="store_true",
+ help = 'do not prompt for any values')
+ post_parser.add_argument('--default-confirm',
+ choices = ['y','Y','n','N'],
+ default = 'y',
+ help = 'default answer to confirmation question')
+ post_parser.add_argument('--priority',
+ choices=config.choices['priority'].values(),
+ help = 'set priority for the new bug')
+ post_parser.add_argument('-S', '--severity',
+ choices=config.choices['severity'],
+ help = 'set the severity for the new bug')
+ post_parser.set_defaults(func = PrettyBugz.post)
+
+def make_search_parser(subparsers):
+ search_parser = subparsers.add_parser('search',
+ help = 'search for bugs in bugzilla')
+ search_parser.add_argument('terms',
+ nargs='*',
+ help = 'strings to search for in title or body')
+ search_parser.add_argument('-o', '--order',
+ choices = config.choices['order'].keys(),
+ default = 'number',
+ help = 'display bugs in this order')
+ search_parser.add_argument('-a', '--assigned-to',
+ help = 'email the bug is assigned to')
+ search_parser.add_argument('-r', '--reporter',
+ help = 'email the bug was reported by')
+ search_parser.add_argument('--cc',
+ help = 'restrict by CC email address')
+ search_parser.add_argument('--commenter',
+ help = 'email that commented the bug')
+ search_parser.add_argument('-s', '--status',
+ action='append',
+ help = 'restrict by status (one or more, use all for all statuses)')
+ search_parser.add_argument('--severity',
+ action='append',
+ choices = config.choices['severity'],
+ help = 'restrict by severity (one or more)')
+ search_parser.add_argument('--priority',
+ action='append',
+ choices = config.choices['priority'].values(),
+ help = 'restrict by priority (one or more)')
+ search_parser.add_argument('-c', '--comments',
+ action='store_true',
+ default=None,
+ help = 'search comments instead of title')
+ search_parser.add_argument('--product',
+ action='append',
+ help = 'restrict by product (one or more)')
+ search_parser.add_argument('-C', '--component',
+ action='append',
+ help = 'restrict by component (1 or more)')
+ search_parser.add_argument('-k', '--keywords',
+ help = 'restrict by keywords')
+ search_parser.add_argument('-w', '--whiteboard',
+ help = 'status whiteboard')
+ search_parser.add_argument('--show-status',
+ action = 'store_true',
+ help='show status of bugs')
+ search_parser.add_argument('--show-url',
+ action = 'store_true',
+ help='show bug id as a url.')
+ search_parser.set_defaults(func = PrettyBugz.search)
+
+def make_parser():
+ parser = argparse.ArgumentParser(
+ epilog = 'use -h after a sub-command for sub-command specific help')
+ parser.add_argument('-b', '--base',
+ default = 'https://bugs.gentoo.org/',
+ help = 'base URL of Bugzilla')
+ parser.add_argument('-u', '--user',
+ help = 'username for commands requiring authentication')
+ parser.add_argument('-p', '--password',
+ help = 'password for commands requiring authentication')
+ parser.add_argument('-H', '--httpuser',
+ help = 'username for basic http auth')
+ parser.add_argument('-P', '--httppassword',
+ help = 'password for basic http auth')
+ parser.add_argument('-f', '--forget',
+ action='store_true',
+ help = 'forget login after execution')
+ parser.add_argument('-q', '--quiet',
+ action='store_true',
+ default = False,
+ help = 'quiet mode')
+ parser.add_argument('--columns',
+ type = int,
+ default = 0,
+ help = 'maximum number of columns output should use')
+ parser.add_argument('--encoding',
+ help = 'output encoding (default: utf-8).')
+ parser.add_argument('--skip-auth',
+ action='store_true',
+ default = False,
+ help = 'skip Authentication.')
+ parser.add_argument('--version',
+ action='version',
+ help='show program version and exit',
+ version='%(prog)s ' + __version__)
+ subparsers = parser.add_subparsers(help = 'help for sub-commands')
+ make_attach_parser(subparsers)
+ make_attachment_parser(subparsers)
+ make_get_parser(subparsers)
+ make_modify_parser(subparsers)
+ make_namedcmd_parser(subparsers)
+ make_post_parser(subparsers)
+ make_search_parser(subparsers)
+ return parser
+
+def get_kwds(args):
+ bugz = {}
+ cmd = {}
+ global_attrs = ['user', 'password', 'httpuser', 'httppassword', 'forget',
+ 'base', 'columns', 'encoding', 'quiet', 'skip_auth']
+ for attr in dir(args):
+ if attr[0] != '_' and attr != 'func':
+ if attr in global_attrs:
+ bugz[attr] = getattr(args,attr)
+ else:
+ cmd[attr] = getattr(args,attr)
+ return bugz, cmd
diff --git a/bugz/config.py b/bugz/config.py
index 4083e48..824c2ad 100644
--- a/bugz/config.py
+++ b/bugz/config.py
@@ -54,6 +54,7 @@ class BugzConfig:
'attach_post': {
'action': 'insert',
+ 'ispatch': '',
'contenttypemethod': 'manual',
'bugid': '',
'description': '',
diff --git a/contrib/zsh-completion b/contrib/zsh-completion
index ee4bef6..c88ebff 100644
--- a/contrib/zsh-completion
+++ b/contrib/zsh-completion
@@ -14,7 +14,7 @@ _bugz() {
'(-P --httppassword)'{-P,--httppassword}'[basic http auth password (if required)]:password: '
'(-f --forget)'{-f,--forget}'[do not remember authentication]'
'--columns[number of columns to use when displaying output]:number: '
- '(--skip-auth)'{--skip-auth}'[do not authenticate]'
+ '--skip-auth[do not authenticate]'
'(-q --quiet)'{-q,--quiet}'[do not display status messages]'
)
_bugz_commands=(
.