def __init__(self, base, user = None, password = None, forget = False, columns = 0, encoding = '', skip_auth = False, quiet = False, httpuser = None, httppassword = None ): self.quiet = quiet self.columns = columns or terminal_width() cp = ConfigParser() cp.read(os.path.expanduser('~/.pybugz')) defrepo = cp.get('Settings', 'default') if not base: base = cp.get(defrepo, 'url') if not user: user = cp.get(defrepo, 'user') password = cp.get(defrepo, 'password') Bugz.__init__(self, base, user, password, forget, skip_auth, httpuser, httppassword) self.log("Using %s " % self.base) if not encoding: try: self.enc = locale.getdefaultlocale()[1] except: self.enc = 'utf-8' if not self.enc: self.enc = 'utf-8' else: self.enc = encoding
def search(self, *args, **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() show_url = kwds['show_url'] del kwds['show_url'] search_opts = sorted([(opt, val) for opt, val in kwds.items() if val != None and opt != 'order']) if not (search_term or search_opts): raise BugzError('Please give search terms or options.') if search_term: log_msg = 'Searching for \'%s\' ' % search_term else: log_msg = 'Searching for bugs ' if search_opts: self.log(log_msg + 'with the following options:') for opt, val in search_opts: self.log(' %-20s = %s' % (opt, val)) else: self.log(log_msg) result = Bugz.search(self, search_term, **kwds) if result == None: raise RuntimeError('Failed to perform search') if len(result) == 0: self.log('No bugs found.') return self.listbugs(result, show_url)
def attach(self, bugid, filename, content_type = 'text/plain', 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)
def namedcmd(self, command, show_status=False, show_url=False): """Run a command stored in Bugzilla by name.""" log_msg = 'Running namedcmd \'%s\''%command result = Bugz.namedcmd(self, command) if result is None: raise RuntimeError('Failed to run command\nWrong namedcmd perhaps?') if len(result) == 0: self.log('No result from command') return self.listbugs(result, show_url, show_status)
def __init__(self, base, user = None, password =None, forget = False, columns = 0, encoding = '', skip_auth = False, quiet = False, httpuser = None, httppassword = None ): self.quiet = quiet self.columns = columns or terminal_width() Bugz.__init__(self, base, user, password, forget, skip_auth, httpuser, httppassword) self.log("Using %s " % self.base) if not encoding: try: self.enc = locale.getdefaultlocale()[1] except: self.enc = 'utf-8' if not self.enc: self.enc = 'utf-8' else: self.enc = encoding
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, patch) if result == True: self.log("'%s' has been attached to bug %s" % (filename, bugid)) else: reason = "" if result and result != False: reason = "\nreason: %s" % result raise RuntimeError("Failed to attach '%s' to bug %s%s" % (filename, bugid, reason))
def attachment(self, attachid, view = False): """ Download or view an attachment given the id.""" self.log('Getting attachment %s' % attachid) result = Bugz.attachment(self, attachid) if not result: raise RuntimeError('Unable to get attachment') action = {True:'Viewing', False:'Saving'} self.log('%s attachment: "%s"' % (action[view], result['filename'])) safe_filename = os.path.basename(re.sub(r'\.\.', '', result['filename'])) if view: print result['fd'].read() else: if os.path.exists(result['filename']): raise RuntimeError('Filename already exists') open(safe_filename, 'wb').write(result['fd'].read())
def attachment(self, attachid, view=False): """ Download or view an attachment given the id.""" self.log('Getting attachment %s' % attachid) result = Bugz.attachment(self, attachid) if not result: raise RuntimeError('Unable to get attachment') action = {True: 'Viewing', False: 'Saving'} self.log('%s attachment: "%s"' % (action[view], result['filename'])) safe_filename = os.path.basename( re.sub(r'\.\.', '', result['filename'])) if view: print result['fd'].read() else: if os.path.exists(result['filename']): raise RuntimeError('Filename already exists') open(safe_filename, 'wb').write(result['fd'].read())
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, patch) if result == True: self.log("'%s' has been attached to bug %s" % (filename, bugid)) else: reason = "" if result and result != False: reason = "\nreason: %s" % result raise RuntimeError("Failed to attach '%s' to bug %s%s" % (filename, bugid, reason))
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(kwds['terms']).strip() del kwds['terms'] show_status = kwds['show_status'] del kwds['show_status'] show_url = kwds['show_url'] del kwds['show_url'] show_only_bug_id = kwds['show_only_bug_id'] del kwds['show_only_bug_id'] search_opts = sorted([(opt, val) for opt, val in kwds.items() if val != None and opt != 'order']) if not (search_term or search_opts): raise BugzError('Please give search terms or options.') if search_term: log_msg = 'Searching for \'%s\' ' % search_term else: log_msg = 'Searching for bugs ' if search_opts: self.log(log_msg + 'with the following options:') for opt, val in search_opts: self.log(' %-20s = %s' % (opt, val)) else: self.log(log_msg) result = Bugz.search(self, search_term, **kwds) if result == None: raise RuntimeError('Failed to perform search') if len(result) == 0: self.log('No bugs found.') return self.listbugs(result, show_url, show_status, show_only_bug_id)
def get(self, bugid, comments = True, attachments = True): """ Fetch bug details given the bug id """ self.log('Getting bug %s ..' % bugid) result = Bugz.get(self, bugid) if result is None: raise RuntimeError('Bug %s not found' % bugid) # Print out all the fields below by extract the text # directly from the tag, and just ignore if we don't # see the tag. FIELDS = ( ('short_desc', 'Title'), ('assigned_to', 'Assignee'), ('creation_ts', 'Reported'), ('delta_ts', 'Updated'), ('bug_status', 'Status'), ('resolution', 'Resolution'), ('bug_file_loc', 'URL'), ('bug_severity', 'Severity'), ('priority', 'Priority'), ('reporter', 'Reporter'), ) MORE_FIELDS = ( ('product', 'Product'), ('component', 'Component'), ('status_whiteboard', 'Whiteboard'), ('keywords', 'Keywords'), ) for field, name in FIELDS + MORE_FIELDS: try: value = result.find('.//%s' % field).text if value is None: continue except AttributeError: continue print '%-12s: %s' % (name, value.encode(self.enc)) # Print out the cc'ed people cced = result.findall('.//cc') for cc in cced: print '%-12s: %s' % ('CC', cc.text) # print out depends dependson = ', '.join([d.text for d in result.findall('.//dependson')]) blocked = ', '.join([d.text for d in result.findall('.//blocked')]) if dependson: print '%-12s: %s' % ('DependsOn', dependson) if blocked: print '%-12s: %s' % ('Blocked', blocked) bug_comments = result.findall('.//long_desc') bug_attachments = result.findall('.//attachment') print '%-12s: %d' % ('Comments', len(bug_comments)) print '%-12s: %d' % ('Attachments', len(bug_attachments)) print if attachments: for attachment in bug_attachments: aid = attachment.find('.//attachid').text desc = attachment.find('.//desc').text when = attachment.find('.//date').text print '[Attachment] [%s] [%s]' % (aid, desc.encode(self.enc)) if comments: i = 0 wrapper = textwrap.TextWrapper(width = self.columns) for comment in bug_comments: try: who = comment.find('.//who').text.encode(self.enc) except AttributeError: # Novell doesn't use 'who' on xml who = "" when = comment.find('.//bug_when').text.encode(self.enc) what = comment.find('.//thetext').text print '\n[Comment #%d] %s : %s' % (i, who, when) print '-' * (self.columns - 1) if what is None: what = '' # print wrapped version for line in what.split('\n'): if len(line) < self.columns: print line.encode(self.enc) else: for shortline in wrapper.wrap(line): print shortline.encode(self.enc) i += 1 print
if 'comment_editor' in kwds: if kwds['comment_editor']: kwds['comment'] = block_edit('Enter comment:') del kwds['comment_editor'] if kwds['fixed']: kwds['status'] = 'RESOLVED' kwds['resolution'] = 'FIXED' del kwds['fixed'] if kwds['invalid']: kwds['status'] = 'RESOLVED' kwds['resolution'] = 'INVALID' del kwds['invalid'] result = Bugz.modify(self, bugid, **kwds) if not result: raise RuntimeError('Failed to modify bug') else: self.log('Modified bug %s with the following fields:' % bugid) for field, value in result: self.log(' %-12s: %s' % (field, value)) def attachment(self, attachid, view = False): """ Download or view an attachment given the id.""" self.log('Getting attachment %s' % attachid) result = Bugz.attachment(self, attachid) if not result: raise RuntimeError('Unable to get attachment')
def get(self, bugid, comments = True, attachments = True): """ Fetch bug details given the bug id """ self.log('Getting bug %s ..' % bugid) result = Bugz.get(self, bugid) if result == None: raise RuntimeError('Bug %s not found' % bugid) # Print out all the fields below by extract the text # directly from the tag, and just ignore if we don't # see the tag. FIELDS = ( ('short_desc', 'Title'), ('assigned_to', 'Assignee'), ('creation_ts', 'Reported'), ('delta_ts', 'Updated'), ('bug_status', 'Status'), ('resolution', 'Resolution'), ('bug_file_loc', 'URL'), ('bug_severity', 'Severity'), ('priority', 'Priority'), ('reporter', 'Reporter'), ) MORE_FIELDS = ( ('product', 'Product'), ('component', 'Component'), ('status_whiteboard', 'Whiteboard'), ('keywords', 'Keywords'), ) for field, name in FIELDS + MORE_FIELDS: try: value = result.find('//%s' % field).text except AttributeError: continue print '%-12s: %s' % (name, value.encode(self.enc)) # Print out the cc'ed people cced = result.findall('.//cc') for cc in cced: print '%-12s: %s' % ('CC', cc.text) # print out depends dependson = ', '.join([d.text for d in result.findall('.//dependson')]) blocked = ', '.join([d.text for d in result.findall('.//blocked')]) if dependson: print '%-12s: %s' % ('DependsOn', dependson) if blocked: print '%-12s: %s' % ('Blocked', blocked) bug_comments = result.findall('//long_desc') bug_attachments = result.findall('//attachment') print '%-12s: %d' % ('Comments', len(bug_comments)) print '%-12s: %d' % ('Attachments', len(bug_attachments)) print '%-12s: %s' % ('URL', '%s?id=%s' % (urljoin(self.base, config.urls['show']), bugid)) print if attachments: for attachment in bug_attachments: aid = attachment.find('.//attachid').text desc = attachment.find('.//desc').text when = attachment.find('.//date').text print '[Attachment] [%s] [%s]' % (aid, desc.encode(self.enc)) if comments: i = 0 wrapper = textwrap.TextWrapper(width = self.columns) for comment in bug_comments: try: who = comment.find('.//who').text.encode(self.enc) except AttributeError: # Novell doesn't use 'who' on xml who = "" when = comment.find('.//bug_when').text.encode(self.enc) what = comment.find('.//thetext').text print '\n[Comment #%d] %s : %s' % (i, who, when) print '-' * (self.columns - 1) if what is None: what = '' # print wrapped version for line in what.split('\n'): if len(line) < self.columns: print line.encode(self.enc) else: for shortline in wrapper.wrap(line): print shortline.encode(self.enc) i += 1 print
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 ): self.quiet = quiet self.columns = columns or terminal_width() Bugz.__init__(self, base, user, password, forget, skip_auth, httpuser, httppassword) self.log("Using %s " % self.base) if not encoding: try: self.enc = locale.getdefaultlocale()[1] except: self.enc = 'utf-8' if not self.enc: self.enc = 'utf-8' else: self.enc = encoding def log(self, status_msg, newline = True): if not self.quiet: if newline: print ' * %s' % status_msg else: print ' * %s' % status_msg, def warn(self, warn_msg): if not self.quiet: print ' ! Warning: %s' % warn_msg def get_input(self, prompt): return raw_input(prompt) def search(self, *args, **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() show_status = kwds['show_status'] del kwds['show_status'] show_url = kwds['show_url'] del kwds['show_url'] search_opts = sorted([(opt, val) for opt, val in kwds.items() if val != None and opt != 'order']) if not (search_term or search_opts): raise BugzError('Please give search terms or options.') if search_term: log_msg = 'Searching for \'%s\' ' % search_term else: log_msg = 'Searching for bugs ' if search_opts: self.log(log_msg + 'with the following options:') for opt, val in search_opts: self.log(' %-20s = %s' % (opt, val)) else: self.log(log_msg) result = Bugz.search(self, search_term, **kwds) if result == None: raise RuntimeError('Failed to perform search') if len(result) == 0: self.log('No bugs found.') return self.listbugs(result, show_url, show_status) search.args = "<search term> [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 result = Bugz.namedcmd(self, command) if result == None: raise RuntimeError('Failed to run command\nWrong namedcmd perhaps?') if len(result) == 0: self.log('No result from command') return self.listbugs(result, show_url, show_status) namedcmd.args = "<command name>" 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) result = Bugz.get(self, bugid) if result == None: raise RuntimeError('Bug %s not found' % bugid) # Print out all the fields below by extract the text # directly from the tag, and just ignore if we don't # see the tag. FIELDS = ( ('short_desc', 'Title'), ('assigned_to', 'Assignee'), ('creation_ts', 'Reported'), ('delta_ts', 'Updated'), ('bug_status', 'Status'), ('resolution', 'Resolution'), ('bug_file_loc', 'URL'), ('bug_severity', 'Severity'), ('priority', 'Priority'), ('reporter', 'Reporter'), ) MORE_FIELDS = ( ('product', 'Product'), ('component', 'Component'), ('status_whiteboard', 'Whiteboard'), ('keywords', 'Keywords'), ) for field, name in FIELDS + MORE_FIELDS: try: value = result.find('//%s' % field).text except AttributeError: continue print '%-12s: %s' % (name, value.encode(self.enc)) # Print out the cc'ed people cced = result.findall('.//cc') for cc in cced: print '%-12s: %s' % ('CC', cc.text) # print out depends dependson = ', '.join([d.text for d in result.findall('.//dependson')]) blocked = ', '.join([d.text for d in result.findall('.//blocked')]) if dependson: print '%-12s: %s' % ('DependsOn', dependson) if blocked: print '%-12s: %s' % ('Blocked', blocked) bug_comments = result.findall('//long_desc') bug_attachments = result.findall('//attachment') print '%-12s: %d' % ('Comments', len(bug_comments)) print '%-12s: %d' % ('Attachments', len(bug_attachments)) print '%-12s: %s' % ('URL', '%s?id=%s' % (urljoin(self.base, config.urls['show']), bugid)) print if attachments: for attachment in bug_attachments: aid = attachment.find('.//attachid').text desc = attachment.find('.//desc').text when = attachment.find('.//date').text print '[Attachment] [%s] [%s]' % (aid, desc.encode(self.enc)) if comments: i = 0 wrapper = textwrap.TextWrapper(width = self.columns) for comment in bug_comments: try: who = comment.find('.//who').text.encode(self.enc) except AttributeError: # Novell doesn't use 'who' on xml who = "" when = comment.find('.//bug_when').text.encode(self.enc) what = comment.find('.//thetext').text print '\n[Comment #%d] %s : %s' % (i, who, when) print '-' * (self.columns - 1) if what is None: what = '' # print wrapped version for line in what.split('\n'): if len(line) < self.columns: print line.encode(self.enc) else: for shortline in wrapper.wrap(line): print shortline.encode(self.enc) i += 1 print get.args = "<bug_id> [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, description_from = None, prodversion = None, append_command = None, dependson = None, blocked = None, batch = False, default_confirm = 'y', priority = None, severity = None): """Post a new bug""" # load description from file if possible if description_from: try: description = open(description_from, 'r').read() except IOError, e: raise BugzError('Unable to read from file: %s: %s' % \ (description_from, e)) if not batch: self.log('Press Ctrl+C at any time to abort.') # # Check all bug fields. # XXX: We use "if not <field>" for mandatory fields # and "if <field> is None" for optional ones. # # check for product if not product: while not product or len(product) < 1: product = self.get_input('Enter product: ') else: self.log('Enter product: %s' % product) # check for version # FIXME: This default behaviour is not too nice. if prodversion is None: prodversion = self.get_input('Enter version (default: unspecified): ') else: self.log('Enter version: %s' % prodversion) # check for component if not component: while not component or len(component) < 1: component = self.get_input('Enter component: ') else: self.log('Enter component: %s' % component) # check for default priority if priority is None: priority_msg ='Enter priority (eg. P2) (optional): ' priority = self.get_input(priority_msg) else: self.log('Enter priority (optional): %s' % priority) # check for default severity if severity is None: severity_msg ='Enter severity (eg. normal) (optional): ' severity = self.get_input(severity_msg) else: self.log('Enter severity (optional): %s' % severity) # check for default assignee if assigned_to is None: assigned_msg ='Enter assignee (eg. [email protected]) (optional): ' assigned_to = self.get_input(assigned_msg) else: self.log('Enter assignee (optional): %s' % assigned_to) # check for CC list if cc is None: cc_msg = 'Enter a CC list (comma separated) (optional): ' cc = self.get_input(cc_msg) else: self.log('Enter a CC list (optional): %s' % cc) # check for optional URL if url is None: url = self.get_input('Enter URL (optional): ') else: self.log('Enter URL (optional): %s' % url) # check for title if not title: while not title or len(title) < 1: title = self.get_input('Enter title: ') else: self.log('Enter title: %s' % title) # check for description if not description: description = block_edit('Enter bug description: ') else: self.log('Enter bug description: %s' % description) if append_command is None: append_command = self.get_input('Append the output of the following command (leave blank for none): ') else: self.log('Append command (optional): %s' % append_command) # check for Keywords list if keywords is None: kwd_msg = 'Enter a Keywords list (comma separated) (optional): ' keywords = self.get_input(kwd_msg) else: self.log('Enter a Keywords list (optional): %s' % keywords) # check for bug dependencies if dependson is None: dependson_msg = 'Enter a list of bug dependencies (comma separated) (optional): ' dependson = self.get_input(dependson_msg) else: self.log('Enter a list of bug dependencies (optional): %s' % dependson) # check for blocker bugs if blocked is None: blocked_msg = 'Enter a list of blocker bugs (comma separated) (optional): ' blocked = self.get_input(blocked_msg) else: self.log('Enter a list of blocker bugs (optional): %s' % blocked) # append the output from append_command to the description if append_command is not None and append_command != '': append_command_output = commands.getoutput(append_command) description = description + '\n\n' + '$ ' + append_command + '\n' + append_command_output # raise an exception if mandatory fields are not specified. if product is None: raise RuntimeError('Product not specified') if component is None: raise RuntimeError('Component not specified') if title is None: raise RuntimeError('Title not specified') if description is None: raise RuntimeError('Description not specified') # set optional fields to their defaults if they are not set. if prodversion is None: prodversion = '' if priority is None: priority = '' if severity is None: severity = '' if assigned_to is None: assigned_to = '' if cc is None: cc = '' if url is None: url = '' if keywords is None: keywords = '' if dependson is None: dependson = '' if blocked is None: blocked = '' # print submission confirmation print '-' * (self.columns - 1) print 'Product : ' + product print 'Version : ' + prodversion print 'Component : ' + component print 'priority : ' + priority print 'severity : ' + severity print 'Assigned to : ' + assigned_to print 'CC : ' + cc print 'URL : ' + url print 'Title : ' + title print 'Description : ' + description print 'Keywords : ' + keywords print 'Depends on : ' + dependson print 'Blocks : ' + blocked print '-' * (self.columns - 1) if not batch: if default_confirm in ['Y','y']: confirm = raw_input('Confirm bug submission (Y/n)? ') else: confirm = raw_input('Confirm bug submission (y/N)? ') if len(confirm) < 1: confirm = default_confirm if confirm[0] not in ('y', 'Y'): self.log('Submission aborted') return result = Bugz.post(self, product, component, title, description, url, assigned_to, cc, keywords, prodversion, dependson, blocked, priority, severity) if result != None and result != 0: self.log('Bug %d submitted' % result) else: raise RuntimeError('Failed to submit bug')
raise BugzError('Failed to get read from file: %s: %s' % \ (comment_from, e)) if 'comment_editor' in kwds: if kwds['comment_editor']: kwds['comment'] = block_edit('Enter comment:', kwds['comment']) del kwds['comment_editor'] del kwds['comment_from'] if 'comment_editor' in kwds: if kwds['comment_editor']: kwds['comment'] = block_edit('Enter comment:') del kwds['comment_editor'] result = Bugz.modify(self, bugid, **kwds) if not result: raise RuntimeError('Failed to modify bug') else: self.log('Modified bug %s with the following fields:' % bugid) for field, value in result: self.log(' %-12s: %s' % (field, value)) modify.args = "<bug_id> [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,