(Steve Dougherty)
2013-07-09: Add initial notification checking support. Add initial notification checking support. Supports multiple matching messages. Comments. Displays recognized requests and gives feedback otherwise.
diff --git a/infocalypse/wot.py b/infocalypse/wot.py --- a/infocalypse/wot.py +++ b/infocalypse/wot.py @@ -61,7 +61,7 @@ HG: The first line has "{0}" added befor HG: The second line should be blank. HG: Following lines are the body of the message. HG: Below is the machine-readable footer describing the request. Modifying it -HG: or putting things below it has the potential to cause problems. +HG: might make it not work. {1}""".format(VCS_PREFIX, footer), from_identifier) # TODO: Abort in the case of a blank message? # Markdown support would be on receiving end. Maybe CLI preview eventually. @@ -108,6 +108,9 @@ def check_notifications(ui, from_identit # http://bugs.python.org/issue917120 reply_type, message_numbers = imap.search(None, '(SUBJECT %s)' % VCS_PREFIX) + # imaplib returns numbers in a singleton string separated by whitespace. + message_numbers = message_numbers[0].split() + # fetch() expects strings for both. Individual message numbers are # separated by commas. It seems desirable to peek because it's not yet # apparent that this is a [vcs] message with YAML. @@ -115,12 +118,9 @@ def check_notifications(ui, from_identit status, subjects = imap.fetch(','.join(message_numbers), r'(body.peek[header.fields Subject])') - # Expecting: - # ('5 (body[HEADER.FIELDS Subject] {47}', 'Subject: [vcs] test\r\n\r\n') - # However see http://bpaste.net/show/112243/ - imaplib does not appear to - # properly parse the closing parenthesis of the name/value pair, - # giving it instead as a single string. - # https://tools.ietf.org/html/rfc2060.html#section-7 + # Expecting 2 list items from imaplib for each message, for example: + # ('5 (body[HEADER.FIELDS Subject] {47}', 'Subject: [vcs] ...\r\n\r\n'), + # ')', # Exclude closing parens, which are of length one. subjects = filter(lambda x: len(x) == 2, subjects) @@ -128,11 +128,76 @@ def check_notifications(ui, from_identit subjects = [x[1] for x in subjects] # Remove field name and trim whitespace. - subjects = [subject.rstrip()[len('Subject: '):] for subject in subjects] + subjects = dict((message_number, subject[len('Subject: '):].rstrip()) for + message_number, subject in zip(message_numbers, subjects)) - for subject in subjects: + for message_number, subject in subjects.iteritems(): if subject.startswith(VCS_PREFIX): - print "Found VCS email '%s'" % subject + # Read the message at this point. + status, fetched = imap.fetch(str(message_number), + r'(body[text] ' + r'body[header.fields From)') + + # Expecting 3 list items, as with the subject fetch above. + body = fetched[0][1] + from_address = fetched[1][1][len('From: '):].rstrip() + + read_message_yaml(ui, from_address, subject, body) + + +def read_message_yaml(ui, from_address, subject, body): + # TODO: Will these line endings always be present? splitlines() doesn't + # seem clean either due to the work required to figure out the index. + # Find start and end in an attempt to tolerate things after the footer. + yaml_start = body.rfind('---\r\n') + yaml_end = body.rfind('...\r\n') + + if yaml_start == -1 or yaml_end == -1: + ui.status("Notification '%s' does not have a request.\n" % subject) + return + + def require(field, request): + if field not in request: + ui.status("Notification '%s' has a properly formatted request " + "that does not include necessary information. ('%s')\n" + % (subject, field)) + return False + return True + + try: + request = yaml.safe_load(body[yaml_start:yaml_end]) + + if not require('vcs', request) or not require('request', request): + return + except yaml.YAMLError, e: + ui.status("Notification '%s' has a request but it is not properly" + " formatted. Details:\n%s\n" % (subject, e)) + return + + if request['vcs'] != 'Infocalypse': + ui.status("Notification '%s' is for '%s', not Infocalypse.\n" + % (subject, request['vcs'])) + return + + if request['request'] == 'pull': + ui.status("Found pull request from '%s':\n" % from_address) + separator = ('-' * len(subject)) + '\n' + + ui.status(separator) + ui.status(subject[len(VCS_PREFIX):] + '\n') + + ui.status(separator) + ui.status(body[:yaml_start]) + ui.status(separator) + + ui.status("To accept this request, pull from: %s\n" + " To your repository: %s\n" % + (request['source'], request['target'])) + return + + ui.status("Notification '%s' has an unrecognized request of type '%s'" + % (subject, request['request'])) + def update_repo_listing(ui, for_identity):