create/edit tickets in a text editor

This commit is contained in:
Romain Bignon 2014-01-21 23:13:39 +01:00
commit 2b0d4e8b91
2 changed files with 163 additions and 68 deletions

View file

@ -19,10 +19,16 @@
from datetime import timedelta
from email import message_from_string, message_from_file
from email.Header import decode_header
from email.mime.text import MIMEText
from smtplib import SMTP
import sys
import os
import re
from weboob.capabilities.base import empty, CapBaseObject
from weboob.capabilities.bugtracker import ICapBugTracker, Query, Update, Project, Issue
from weboob.capabilities.bugtracker import ICapBugTracker, Query, Update, Project, Issue, IssueError
from weboob.tools.application.repl import ReplApplication, defaultcount
from weboob.tools.application.formatters.iformatter import IFormatter, PrettyFormatter
from weboob.tools.misc import html2text
@ -223,6 +229,11 @@ class BoobTracker(ReplApplication):
self.do('update_issue', id, update, backends=backend_name).wait()
def complete_remove(self, text, line, *ignored):
args = line.split(' ')
if len(args) == 2:
return self._complete_object()
def do_remove(self, line):
"""
remove ISSUE
@ -246,63 +257,154 @@ class BoobTracker(ReplApplication):
for obj in objects_list:
if obj.name.lower() == name.lower():
return obj
print 'Error: "%s" is not found' % name
return None
def prompt_issue(self, issue, requested_key=None, requested_value=None):
if not name:
return None
raise ValueError('"%s" is not found' % name)
def issue2text(self, issue, backend=None):
if backend is not None and 'username' in backend.config:
sender = backend.config['username'].get()
else:
sender = os.environ.get('USERNAME', 'boobtracker')
output = u'From: %s\n' % sender
for key, (list_name, is_list_object) in self.ISSUE_FIELDS:
if requested_key and requested_key != key:
if not self.interactive:
value = getattr(self.options, key)
if not value:
value = getattr(issue, key)
if not value:
value = ''
elif hasattr(value, 'name'):
value = value.name
if list_name is not None:
objects_list = getattr(issue.project, list_name)
if len(objects_list) == 0:
continue
output += '%s: %s\n' % (key.capitalize(), value)
if list_name is not None:
availables = ', '.join(['<%s>' % (o if isinstance(o, basestring) else o.name)
for o in objects_list])
output += 'X-Available-%s: %s\n' % (key.capitalize(), availables)
for key, value in issue.fields.iteritems():
output += '%s: %s\n' % (key, value or '')
output += '\n%s' % (issue.body or 'Please write your bug report here.')
return output
def text2issue(self, issue, m):
# XXX HACK to support real incoming emails
if 'Subject' in m:
m['Title'] = m['Subject']
for key, (list_name, is_list_object) in self.ISSUE_FIELDS:
value = m.get(key)
if value is None:
continue
if requested_value:
value = requested_value
elif not self.interactive:
value = getattr(self.options, key)
else:
value = None
if sys.stdin.isatty():
default = getattr(issue, key)
if not default:
default = None
elif 'name' in dir(default):
default = default.name
if list_name is None:
if value is not None:
setattr(issue, key, value)
print '%s: %s' % (key.capitalize(), value)
continue
setattr(issue, key, self.ask(key.capitalize(), default=default))
new_value = u''
for part in decode_header(value):
if part[1]:
new_value += unicode(part[0], part[1])
else:
objects_list = getattr(issue.project, list_name)
if len(objects_list) == 0:
new_value += unicode(part[0])
value = new_value
if is_list_object:
objects_list = getattr(issue.project, list_name)
value = self.get_list_item(objects_list, value)
setattr(issue, key, value)
for key in issue.fields.keys():
value = m.get(key)
if value is not None:
issue.fields[key] = value.decode('utf-8')
content = u''
for part in m.walk():
if part.get_content_type() == 'text/plain':
s = part.get_payload(decode=True)
charsets = part.get_charsets() + m.get_charsets()
for charset in charsets:
try:
if charset is not None:
content += unicode(s, charset)
else:
content += unicode(s)
except UnicodeError as e:
self.logger.warning('Unicode error: %s' % e)
continue
except Exception as e:
self.logger.exception(e)
continue
else:
break
print '----------'
if value is not None:
if is_list_object:
value = self.get_list_item(objects_list, value)
if value is not None:
setattr(issue, key, value)
print '%s: %s' % (key.capitalize(), value.name)
continue
issue.body = content
while value is None:
print 'Availables:', ', '.join([(o if isinstance(o, basestring) else o.name) for o in objects_list])
if is_list_object and getattr(issue, key):
default = getattr(issue, key).name
else:
default = getattr(issue, key) or ''
text = self.ask(key.capitalize(), default=default)
if not text:
break
if is_list_object:
value = self.get_list_item(objects_list, text)
else:
value = text
emails = re.findall('<(.*@.*)>', m['From'] or '')
if len(emails) > 0:
return emails[0]
if value is not None:
setattr(issue, key, value)
def edit_issue(self, issue, edit=True):
backend = self.weboob.get_backend(issue.backend)
content = self.issue2text(issue, backend)
while True:
if sys.stdin.isatty():
content = self.acquire_input(content, {'vim': "-c 'set ft=mail'"})
m = message_from_string(content.encode('utf-8'))
else:
m = message_from_file(sys.stdin)
try:
email_to = self.text2issue(issue, m)
except ValueError as e:
if not sys.stdin.isatty():
raise
raw_input("%s -- Press Enter to continue..." % e)
continue
try:
issue = backend.post_issue(issue)
print 'Issue %s %s' % (self.formatter.colored(issue.fullid, 'red', 'bold'),
'updated' if edit else 'created')
if edit:
self.format(issue)
elif email_to:
self.send_notification(email_to, issue)
return 0
except IssueError as e:
if not sys.stdin.isatty():
raise
raw_input("%s -- Press Enter to continue..." % e)
def send_notification(self, email_to, issue):
text = """Hi,
You have successfuly created this ticket on the Weboob tracker:
%s
You can follow your bug report on this page:
https://symlink.me/issues/%s
Regards,
Weboob Team
""" % (issue.title, issue.id)
msg = MIMEText(text)
msg['Subject'] = 'Issue #%s reported' % issue.id
msg['From'] = 'Weboob <weboob@weboob.org>'
msg['To'] = email_to
s = SMTP('localhost')
s.sendmail('weboob@weboob.org', [email_to], msg.as_string())
s.quit()
def do_post(self, line):
"""
@ -324,19 +426,13 @@ class BoobTracker(ReplApplication):
project, backend_name = self.parse_id(line, unique_backend=True)
backend = self.weboob.get_backend(backend_name)
issue = backend.create_issue(project)
issue.backend = backend.name
self.prompt_issue(issue)
if sys.stdin.isatty():
print '----------'
print 'Please enter the content of this new issue.'
issue.body = self.acquire_input()
return self.edit_issue(issue, edit=False)
for backend, issue in self.weboob.do('post_issue', issue, backends=backend):
if issue:
print 'Issue %s%s@%s%s created' % (self.BOLD, issue.id, issue.backend, self.NC)
def complete_remove(self, text, line, *ignored):
def complete_edit(self, text, line, *ignored):
args = line.split(' ')
if len(args) == 2:
return self._complete_object()
@ -361,12 +457,7 @@ class BoobTracker(ReplApplication):
print >>sys.stderr, 'Issue not found: %s' % _id
return 3
self.prompt_issue(issue, key, value)
for backend, i in self.weboob.do('post_issue', issue, backends=issue.backend):
if i:
print 'Issue %s%s@%s%s updated' % (self.BOLD, issue.id, issue.backend, self.NC)
self.format(i)
return self.edit_issue(issue, edit=True)
def complete_attach(self, text, line, *ignored):
args = line.split(' ')

View file

@ -453,15 +453,19 @@ class ConsoleApplication(BaseApplication):
return v.get()
def acquire_input(self, content=None):
def acquire_input(self, content=None, editor_params=None):
editor = os.getenv('EDITOR', 'vi')
if sys.stdin.isatty() and editor:
with NamedTemporaryFile() as f:
filename = f.name
if content is not None:
if isinstance(content, unicode):
content = content.encode(sys.stdin.encoding or locale.getpreferredencoding())
f.write(content)
f.flush()
os.system("%s %s" % (editor, filename))
if editor_params is not None and editor in editor_params:
params = editor_params[editor]
os.system("%s %s %s" % (editor, params, filename))
f.seek(0)
text = f.read()
else: