def build_statusbar(self): """construct and return statusbar widget""" info = {} cb = self.current_buffer btype = None if cb is not None: info = cb.get_info() btype = cb.modename info['buffer_no'] = self.buffers.index(cb) info['buffer_type'] = btype info['total_messages'] = self.dbman.count_messages('*') info['pending_writes'] = len(self.dbman.writequeue) lefttxt = righttxt = u'' if cb is not None: lefttxt, righttxt = settings.get(btype + '_statusbar', (u'', u'')) lefttxt = string_decode(lefttxt, 'UTF-8') lefttxt = lefttxt.format(**info) righttxt = string_decode(righttxt, 'UTF-8') righttxt = righttxt.format(**info) footerleft = urwid.Text(lefttxt, align='left') pending_writes = len(self.dbman.writequeue) if pending_writes > 0: righttxt = ('|' * pending_writes) + ' ' + righttxt footerright = urwid.Text(righttxt, align='right') columns = urwid.Columns([ footerleft, ('fixed', len(righttxt), footerright)]) footer_att = settings.get_theming_attribute('global', 'footer') return urwid.AttrMap(columns, footer_att)
def decode_header(header, normalize=False): """ decode a header value to a unicode string values are usually a mixture of different substrings encoded in quoted printable using diffetrent encodings. This turns it into a single unicode string :param header: the header value :type header: str :param normalize: replace trailing spaces after newlines :type normalize: bool :rtype: unicode """ # If the value isn't ascii as RFC2822 prescribes, # we just return the unicode bytestring as is value = string_decode(header) # convert to unicode try: value = value.encode('ascii') except UnicodeEncodeError: return value # otherwise we interpret RFC2822 encoding escape sequences valuelist = email.header.decode_header(header) decoded_list = [] for v, enc in valuelist: v = string_decode(v, enc) decoded_list.append(string_sanitize(v)) value = u' '.join(decoded_list) if normalize: value = re.sub(r'\n\s+', r' ', value) return value
def build_statusbar(self): """construct and return statusbar widget""" info = {} cb = self.current_buffer btype = None if cb is not None: info = cb.get_info() btype = cb.modename info["buffer_no"] = self.buffers.index(cb) info["buffer_type"] = btype info["total_messages"] = self.dbman.count_messages("*") info["pending_writes"] = len(self.dbman.writequeue) lefttxt = righttxt = u"" if cb is not None: lefttxt, righttxt = settings.get(btype + "_statusbar", (u"", u"")) lefttxt = string_decode(lefttxt, "UTF-8") lefttxt = lefttxt.format(**info) righttxt = string_decode(righttxt, "UTF-8") righttxt = righttxt.format(**info) footerleft = urwid.Text(lefttxt, align="left") pending_writes = len(self.dbman.writequeue) if pending_writes > 0: righttxt = ("|" * pending_writes) + " " + righttxt footerright = urwid.Text(righttxt, align="right") columns = urwid.Columns([footerleft, ("fixed", len(righttxt), footerright)]) footer_att = settings.get_theming_attribute("global", "footer") return urwid.AttrMap(columns, footer_att)
def build_statusbar(self): """construct and return statusbar widget""" info = {} cb = self.current_buffer btype = None if cb is not None: info = cb.get_info() btype = cb.modename info['buffer_no'] = self.buffers.index(cb) info['buffer_type'] = btype info['total_messages'] = self.dbman.count_messages('*') info['pending_writes'] = len(self.dbman.writequeue) info['input_queue'] = ' '.join(self.input_queue) lefttxt = righttxt = u'' if cb is not None: lefttxt, righttxt = settings.get(btype + '_statusbar', (u'', u'')) lefttxt = string_decode(lefttxt, 'UTF-8') lefttxt = lefttxt.format(**info) righttxt = string_decode(righttxt, 'UTF-8') righttxt = righttxt.format(**info) footerleft = urwid.Text(lefttxt, align='left') pending_writes = len(self.dbman.writequeue) if pending_writes > 0: righttxt = ('|' * pending_writes) + ' ' + righttxt footerright = urwid.Text(righttxt, align='right') columns = urwid.Columns([ footerleft, ('fixed', len(righttxt), footerright)]) footer_att = settings.get_theming_attribute('global', 'footer') return urwid.AttrMap(columns, footer_att)
def openEnvelopeFromTmpfile(): # This parses the input from the tempfile. # we do this ourselves here because we want to be able to # just type utf-8 encoded stuff into the tempfile and let alot # worry about encodings. # get input f = open(tf.name) os.unlink(tf.name) enc = settings.config.get('general', 'editor_writes_encoding') template = string_decode(f.read(), enc) f.close() # call post-edit translate hook translate = settings.config.get_hook('post_edit_translate') if translate: template = translate(template, ui=ui, dbm=ui.dbman, aman=ui.accountman, log=ui.logger, config=settings.config) self.envelope.parse_template(template) if self.openNew: ui.buffer_open(buffers.EnvelopeBuffer(ui, self.envelope)) else: ebuffer.envelope = self.envelope ebuffer.rebuild()
def __init__(self, completer, on_exit, on_error=None, edit_text=u'', history=None, **kwargs): """ :param completer: completer to use :type completer: alot.completion.Completer :param on_exit: "enter"-callback that interprets the input (str) :type on_exit: callable :param on_error: callback that handles :class:`alot.errors.CompletionErrors` :type on_error: callback :param edit_text: initial text :type edit_text: str :param history: initial command history :type history: list or str """ self.completer = completer self.on_exit = on_exit self.on_error = on_error self.history = list(history) # we temporarily add stuff here self.historypos = None if not isinstance(edit_text, unicode): edit_text = string_decode(edit_text) self.start_completion_pos = len(edit_text) self.completions = None urwid.Edit.__init__(self, edit_text=edit_text, **kwargs)
def __init__(self, completer, on_exit, on_error=None, edit_text='', history=None, **kwargs): """ :param completer: completer to use :type completer: alot.completion.Completer :param on_exit: "enter"-callback that interprets the input (str) :type on_exit: callable :param on_error: callback that handles :class:`alot.errors.CompletionErrors` :type on_error: callback :param edit_text: initial text :type edit_text: str :param history: initial command history :type history: list or str """ self.completer = completer self.on_exit = on_exit self.on_error = on_error self.history = list(history) # we temporarily add stuff here self.historypos = None if not isinstance(edit_text, str): edit_text = string_decode(edit_text) self.start_completion_pos = len(edit_text) self.completions = None urwid.Edit.__init__(self, edit_text=edit_text, **kwargs)
def represent_datetime(self, d): """ turns a given datetime obj into a unicode string representation. This will: 1) look if a fixed 'timestamp_format' is given in the config 2) check if a 'timestamp_format' hook is defined 3) use :func:`~alot.helper.pretty_datetime` as fallback """ fixed_format = self.get('timestamp_format') if fixed_format: rep = string_decode(d.strftime(fixed_format), 'UTF-8') else: format_hook = self.get_hook('timestamp_format') if format_hook: rep = string_decode(format_hook(d), 'UTF-8') else: rep = pretty_datetime(d) return rep
def __init__(self, completer, on_exit, edit_text=u"", history=None, **kwargs): self.completer = completer self.on_exit = on_exit self.history = list(history) # we temporarily add stuff here self.historypos = None if not isinstance(edit_text, unicode): edit_text = string_decode(edit_text) self.start_completion_pos = len(edit_text) self.completions = None urwid.Edit.__init__(self, edit_text=edit_text, **kwargs)
def decode_header(header, normalize=False): """ decode a header value to a unicode string values are usually a mixture of different substrings encoded in quoted printable using different encodings. This turns it into a single unicode string :param header: the header value :type header: str :param normalize: replace trailing spaces after newlines :type normalize: bool :rtype: unicode """ # If the value isn't ascii as RFC2822 prescribes, # we just return the unicode bytestring as is value = string_decode(header) # convert to unicode try: value = value.encode('ascii') except UnicodeEncodeError: return value # some mailers send out incorrectly escaped headers # and double quote the escaped realname part again. remove those # RFC: 2047 regex = r'"(=\?.+?\?.+?\?[^ ?]+\?=)"' value = re.sub(regex, r'\1', value) logging.debug("unquoted header: |%s|", value) # otherwise we interpret RFC2822 encoding escape sequences valuelist = email.header.decode_header(value) decoded_list = [] for v, enc in valuelist: v = string_decode(v, enc) decoded_list.append(string_sanitize(v)) value = u' '.join(decoded_list) if normalize: value = re.sub(r'\n\s+', r' ', value) return value
def decode_header(header, normalize=False): """ decode a header value to a unicode string values are usually a mixture of different substrings encoded in quoted printable using different encodings. This turns it into a single unicode string :param header: the header value :type header: str :param normalize: replace trailing spaces after newlines :type normalize: bool :rtype: unicode """ # If the value isn't ascii as RFC2822 prescribes, # we just return the unicode bytestring as is value = string_decode(header) # convert to unicode try: value = value.encode('ascii') except UnicodeEncodeError: return value # some mailers send out incorrectly escaped headers # and double quote the escaped realname part again. remove those # RFC: 2047 regex = r'"(=\?.+?\?.+?\?[^ ?]+\?=)"' value = re.sub(regex, r'\1', value) logging.debug("unquoted header: |%s|", value) # otherwise we interpret RFC2822 encoding escape sequences valuelist = email.header.decode_header(value) decoded_list = [] for v, enc in valuelist: v = string_decode(v, enc) decoded_list.append(string_sanitize(v)) value = ' '.join(decoded_list) if normalize: value = re.sub(r'\n\s+', r' ', value) return value
def _get_raw_html(msg): mail = msg.get_email() for part in mail.walk(): ctype = part.get_content_type() if ctype != "text/html": continue cd = part.get('Content-Disposition', '') if cd.startswith('attachment'): continue enc = part.get_content_charset() or 'utf-8' raw = string_decode(part.get_payload(decode=True), enc) return string_sanitize(raw) return None
def __init__(self, completer, on_exit, edit_text=u'', history=None, **kwargs): self.completer = completer self.on_exit = on_exit self.history = list(history) # we temporarily add stuff here self.historypos = None if not isinstance(edit_text, unicode): edit_text = string_decode(edit_text) self.start_completion_pos = len(edit_text) self.completions = None urwid.Edit.__init__(self, edit_text=edit_text, **kwargs)
def openEnvelopeFromTmpfile(): # This parses the input from the tempfile. # we do this ourselves here because we want to be able to # just type utf-8 encoded stuff into the tempfile and let alot # worry about encodings. # get input # tempfile will be removed on buffer cleanup f = open(self.envelope.tmpfile.name) enc = settings.get('editor_writes_encoding') template = string_decode(f.read(), enc) f.close() # call post-edit translate hook translate = settings.get_hook('post_edit_translate') if translate: template = translate(template, ui=ui, dbm=ui.dbman) self.envelope.parse_template(template, only_body=self.edit_only_body) if self.openNew: ui.buffer_open(buffers.EnvelopeBuffer(ui, self.envelope)) else: ebuffer.envelope = self.envelope ebuffer.rebuild()
def extract_body(mail, types=None): """ returns a body text string for given mail. If types is `None`, `text/*` is used: The exact preferred type is specified by the prefer_plaintext config option which defaults to text/html. :param mail: the mail to use :type mail: :class:`email.Message` :param types: mime content types to use for body string :type types: list of str """ preferred = 'text/plain' if settings.get('prefer_plaintext') else 'text/html' has_preferred = False # see if the mail has our preferred type if types == None: has_preferred = list(typed_subpart_iterator(mail, *preferred.split('/'))) body_parts = [] for part in mail.walk(): ctype = part.get_content_type() if types is not None: if ctype not in types: continue cd = part.get('Content-Disposition', '') if cd.startswith('attachment'): continue # if the mail has our preferred type, we only keep this type # note that if types != None, has_preferred always stays False if has_preferred and ctype != preferred: continue enc = part.get_content_charset() or 'ascii' raw_payload = part.get_payload(decode=True) if ctype == 'text/plain': raw_payload = string_decode(raw_payload, enc) body_parts.append(string_sanitize(raw_payload)) else: #get mime handler key = 'copiousoutput' handler, entry = settings.mailcap_find_match(ctype, key=key) tempfile_name = None stdin = None if entry: handler_raw_commandstring = entry['view'] # in case the mailcap defined command contains no '%s', # we pipe the files content to the handling command via stdin if '%s' in handler_raw_commandstring: # open tempfile, respect mailcaps nametemplate nametemplate = entry.get('nametemplate', '%s') prefix, suffix = parse_mailcap_nametemplate(nametemplate) tmpfile = tempfile.NamedTemporaryFile(delete=False, prefix=prefix, suffix=suffix) # write payload to tmpfile tmpfile.write(raw_payload) tmpfile.close() tempfile_name = tmpfile.name else: stdin = raw_payload # read parameter, create handler command parms = tuple(map('='.join, part.get_params())) # create and call external command cmd = mailcap.subst(entry['view'], ctype, filename=tempfile_name, plist=parms) logging.debug('command: %s' % cmd) logging.debug('parms: %s' % str(parms)) cmdlist = split_commandstring(cmd) # call handler rendered_payload, errmsg, retval = helper.call_cmd(cmdlist, stdin=stdin) # remove tempfile if tempfile_name: os.unlink(tempfile_name) if rendered_payload: # handler had output body_parts.append(string_sanitize(rendered_payload)) return u'\n\n'.join(body_parts)
class ComposeCommand(Command): """compose a new email""" def __init__(self, envelope=None, headers={}, template=None, sender=u'', subject=u'', to=[], cc=[], bcc=[], attach=None, omit_signature=False, spawn=None, **kwargs): """ :param envelope: use existing envelope :type envelope: :class:`~alot.db.envelope.Envelope` :param headers: forced header values :type header: doct (str->str) :param template: name of template to parse into the envelope after creation. This should be the name of a file in your template_dir :type template: str :param sender: From-header value :type sender: str :param subject: Subject-header value :type subject: str :param to: To-header value :type to: str :param cc: Cc-header value :type cc: str :param bcc: Bcc-header value :type bcc: str :param attach: Path to files to be attached (globable) :type attach: str :param omit_signature: do not attach/append signature :type omit_signature: bool :param spawn: force spawning of editor in a new terminal :type spawn: bool """ Command.__init__(self, **kwargs) self.envelope = envelope self.template = template self.headers = headers self.sender = sender self.subject = subject self.to = to self.cc = cc self.bcc = bcc self.attach = attach self.omit_signature = omit_signature self.force_spawn = spawn @inlineCallbacks def apply(self, ui): if self.envelope is None: self.envelope = Envelope() if self.template is not None: #get location of tempsdir, containing msg templates tempdir = settings.get('template_dir') tempdir = os.path.expanduser(tempdir) if not tempdir: xdgdir = os.environ.get('XDG_CONFIG_HOME', os.path.expanduser('~/.config')) tempdir = os.path.join(xdgdir, 'alot', 'templates') path = os.path.expanduser(self.template) if not os.path.dirname(path): # use tempsdir if not os.path.isdir(tempdir): ui.notify('no templates directory: %s' % tempdir, priority='error') return path = os.path.join(tempdir, path) if not os.path.isfile(path): ui.notify('could not find template: %s' % path, priority='error') return try: self.envelope.parse_template(open(path).read()) except Exception, e: ui.notify(str(e), priority='error') return # set forced headers for key, value in self.headers.items(): self.envelope.add(key, value) # set forced headers for separate parameters if self.sender: self.envelope.add('From', self.sender) if self.subject: self.envelope.add('Subject', self.subject) if self.to: self.envelope.add('To', ','.join(self.to)) if self.cc: self.envelope.add('Cc', ','.join(self.cc)) if self.bcc: self.envelope.add('Bcc', ','.join(self.bcc)) # get missing From header if not 'From' in self.envelope.headers: accounts = settings.get_accounts() if len(accounts) == 1: a = accounts[0] fromstring = "%s <%s>" % (a.realname, a.address) self.envelope.add('From', fromstring) else: cmpl = AccountCompleter() fromaddress = yield ui.prompt('From', completer=cmpl, tab=1) if fromaddress is None: ui.notify('canceled') return self.envelope.add('From', fromaddress) # add signature if not self.omit_signature: name, addr = email.Utils.parseaddr(self.envelope['From']) account = settings.get_account_by_address(addr) if account is not None: if account.signature: logging.debug('has signature') sig = os.path.expanduser(account.signature) if os.path.isfile(sig): logging.debug('is file') if account.signature_as_attachment: name = account.signature_filename or None self.envelope.attach(sig, filename=name) logging.debug('attached') else: sigcontent = open(sig).read() enc = helper.guess_encoding(sigcontent) mimetype = helper.guess_mimetype(sigcontent) if mimetype.startswith('text'): sigcontent = helper.string_decode( sigcontent, enc) self.envelope.body += '\n' + sigcontent else: ui.notify('could not locate signature: %s' % sig, priority='error') if (yield ui.choice('send without signature?', 'yes', 'no')) == 'no': return # Figure out whether we should GPG sign messages by default # and look up key if so sender = self.envelope.get('From') name, addr = email.Utils.parseaddr(sender) account = settings.get_account_by_address(addr) if account: self.envelope.sign = account.sign_by_default self.envelope.sign_key = account.gpg_key # get missing To header if 'To' not in self.envelope.headers: allbooks = not settings.get('complete_matching_abook_only') logging.debug(allbooks) if account is not None: abooks = settings.get_addressbooks(order=[account], append_remaining=allbooks) logging.debug(abooks) completer = ContactsCompleter(abooks) else: completer = None to = yield ui.prompt('To', completer=completer) if to is None: ui.notify('canceled') return self.envelope.add('To', to.strip(' \t\n,')) if settings.get('ask_subject') and \ not 'Subject' in self.envelope.headers: subject = yield ui.prompt('Subject') logging.debug('SUBJECT: "%s"' % subject) if subject is None: ui.notify('canceled') return self.envelope.add('Subject', subject) if settings.get('compose_ask_tags'): comp = TagsCompleter(ui.dbman) tagsstring = yield ui.prompt('Tags', completer=comp) tags = filter(lambda x: x, tagsstring.split(',')) if tags is None: ui.notify('canceled') return self.envelope.tags = tags if self.attach: for gpath in self.attach: for a in glob.glob(gpath): self.envelope.attach(a) logging.debug('attaching: ' + a) cmd = commands.envelope.EditCommand(envelope=self.envelope, spawn=self.force_spawn, refocus=False) ui.apply_command(cmd)
def extract_body(mail, types=None): """ returns a body text string for given mail. If types is `None`, `text/*` is used: In case mail has a `text/html` part, it is prefered over `text/plain` parts. :param mail: the mail to use :type mail: :class:`email.Message` :param types: mime content types to use for body string :type types: list of str """ html = list(typed_subpart_iterator(mail, 'text', 'html')) # if no specific types are given, we favor text/html over text/plain drop_plaintext = False if html and not types: drop_plaintext = True body_parts = [] for part in mail.walk(): ctype = part.get_content_type() if types is not None: if ctype not in types: continue cd = part.get('Content-Disposition', '') if cd.startswith('attachment'): continue enc = part.get_content_charset() or 'ascii' raw_payload = part.get_payload(decode=True) if ctype == 'text/plain' and not drop_plaintext: raw_payload = string_decode(raw_payload, enc) body_parts.append(string_sanitize(raw_payload)) else: #get mime handler key = 'copiousoutput' handler, entry = settings.mailcap_find_match(ctype, key=key) if entry: # open tempfile, respect mailcaps nametemplate nametemplate = entry.get('nametemplate', '%s') prefix, suffix = parse_mailcap_nametemplate(nametemplate) tmpfile = tempfile.NamedTemporaryFile(delete=False, prefix=prefix, suffix=suffix) # write payload to tmpfile tmpfile.write(raw_payload) tmpfile.close() # read parameter, create handler command parms = tuple(map('='.join, part.get_params())) # create and call external command cmd = mailcap.subst(entry['view'], ctype, filename=tmpfile.name, plist=parms) logging.debug('command: %s' % cmd) logging.debug('parms: %s' % str(parms)) cmdlist = split_commandstring(cmd) # call handler rendered_payload, errmsg, retval = helper.call_cmd(cmdlist) # remove tempfile os.unlink(tmpfile.name) if rendered_payload: # handler had output body_parts.append(string_sanitize(rendered_payload)) return u'\n\n'.join(body_parts)
def apply(self, ui): if self.envelope is None: if self.rest: if self.rest.startswith('mailto'): self.envelope = mailto_to_envelope(self.rest) else: self.envelope = Envelope() self.envelope.add('To', self.rest) else: self.envelope = Envelope() if self.template is not None: # get location of tempsdir, containing msg templates tempdir = settings.get('template_dir') tempdir = os.path.expanduser(tempdir) if not tempdir: xdgdir = os.environ.get('XDG_CONFIG_HOME', os.path.expanduser('~/.config')) tempdir = os.path.join(xdgdir, 'alot', 'templates') path = os.path.expanduser(self.template) if not os.path.dirname(path): # use tempsdir if not os.path.isdir(tempdir): ui.notify('no templates directory: %s' % tempdir, priority='error') return path = os.path.join(tempdir, path) if not os.path.isfile(path): ui.notify('could not find template: %s' % path, priority='error') return try: self.envelope.parse_template(open(path).read()) except Exception as e: ui.notify(str(e), priority='error') return # set forced headers for key, value in self.headers.items(): self.envelope.add(key, value) # set forced headers for separate parameters if self.sender: self.envelope.add('From', self.sender) if self.subject: self.envelope.add('Subject', self.subject) if self.to: self.envelope.add('To', ','.join(self.to)) if self.cc: self.envelope.add('Cc', ','.join(self.cc)) if self.bcc: self.envelope.add('Bcc', ','.join(self.bcc)) # get missing From header if not 'From' in self.envelope.headers: accounts = settings.get_accounts() if len(accounts) == 1: a = accounts[0] fromstring = "%s <%s>" % (a.realname, a.address) self.envelope.add('From', fromstring) else: cmpl = AccountCompleter() fromaddress = yield ui.prompt('From', completer=cmpl, tab=1) if fromaddress is None: raise CommandCanceled() self.envelope.add('From', fromaddress) # add signature if not self.omit_signature: name, addr = email.Utils.parseaddr(self.envelope['From']) account = settings.get_account_by_address(addr) if account is not None: if account.signature: logging.debug('has signature') sig = os.path.expanduser(account.signature) if os.path.isfile(sig): logging.debug('is file') if account.signature_as_attachment: name = account.signature_filename or None self.envelope.attach(sig, filename=name) logging.debug('attached') else: sigcontent = open(sig).read() enc = helper.guess_encoding(sigcontent) mimetype = helper.guess_mimetype(sigcontent) if mimetype.startswith('text'): sigcontent = helper.string_decode( sigcontent, enc) self.envelope.body += '\n' + sigcontent else: ui.notify('could not locate signature: %s' % sig, priority='error') if (yield ui.choice('send without signature?', 'yes', 'no')) == 'no': return # Figure out whether we should GPG sign messages by default # and look up key if so sender = self.envelope.get('From') name, addr = email.Utils.parseaddr(sender) account = settings.get_account_by_address(addr) if account: self.envelope.sign = account.sign_by_default self.envelope.sign_key = account.gpg_key # get missing To header if 'To' not in self.envelope.headers: allbooks = not settings.get('complete_matching_abook_only') logging.debug(allbooks) if account is not None: abooks = settings.get_addressbooks(order=[account], append_remaining=allbooks) logging.debug(abooks) completer = ContactsCompleter(abooks) else: completer = None to = yield ui.prompt('To', completer=completer) if to is None: raise CommandCanceled() self.envelope.add('To', to.strip(' \t\n,')) if settings.get('ask_subject') and \ not 'Subject' in self.envelope.headers: subject = yield ui.prompt('Subject') logging.debug('SUBJECT: "%s"' % subject) if subject is None: raise CommandCanceled() self.envelope.add('Subject', subject) if settings.get('compose_ask_tags'): comp = TagsCompleter(ui.dbman) tagsstring = yield ui.prompt('Tags', completer=comp) tags = filter(lambda x: x, tagsstring.split(',')) if tags is None: raise CommandCanceled() self.envelope.tags = tags if self.attach: for gpath in self.attach: for a in glob.glob(gpath): self.envelope.attach(a) logging.debug('attaching: ' + a) cmd = commands.envelope.EditCommand(envelope=self.envelope, spawn=self.force_spawn, refocus=False) ui.apply_command(cmd)
def extract_body(mail, types=None): """ returns a body text string for given mail. If types is `None`, `text/*` is used: In case mail has a `text/html` part, it is prefered over `text/plain` parts. :param mail: the mail to use :type mail: :class:`email.Message` :param types: mime content types to use for body string :type types: list of str """ html = list(typed_subpart_iterator(mail, 'text', 'html')) # if no specific types are given, we favor text/html over text/plain drop_plaintext = False if html and not types: drop_plaintext = True body_parts = [] for part in mail.walk(): ctype = part.get_content_type() logging.debug(ctype) if types is not None: if ctype not in types: continue cd = part.get('Content-Disposition', '') if cd.startswith('attachment'): continue enc = part.get_content_charset() or 'ascii' raw_payload = part.get_payload(decode=True) if ctype == 'text/plain' and not drop_plaintext: raw_payload = string_decode(raw_payload, enc) body_parts.append(string_sanitize(raw_payload)) else: #get mime handler key = 'copiousoutput' handler, entry = settings.mailcap_find_match(ctype, key=key) if entry: # open tempfile, respect mailcaps nametemplate nametemplate = entry.get('nametemplate', '%s') prefix, suffix = parse_mailcap_nametemplate(nametemplate) tmpfile = tempfile.NamedTemporaryFile(delete=False, prefix=prefix, suffix=suffix) # write payload to tmpfile tmpfile.write(raw_payload) tmpfile.close() # read parameter, create handler command parms = tuple(map('='.join, part.get_params())) # create and call external command cmd = mailcap.subst(entry['view'], ctype, filename=tmpfile.name, plist=parms) logging.debug('command: %s' % cmd) logging.debug('parms: %s' % str(parms)) cmdlist = split_commandstring(cmd) # call handler rendered_payload, errmsg, retval = helper.call_cmd(cmdlist) # remove tempfile os.unlink(tmpfile.name) if rendered_payload: # handler had output body_parts.append(string_sanitize(rendered_payload)) return u'\n\n'.join(body_parts)
def __str__(self): desc = '%s:%s (%s)' % (self.get_content_type(), self.get_filename(), helper.humanize_size(self.get_size())) return string_decode(desc)
def extract_body(mail, types=None): """ returns a body text string for given mail. If types is `None`, `text/*` is used: The exact preferred type is specified by the prefer_plaintext config option which defaults to text/html. :param mail: the mail to use :type mail: :class:`email.Message` :param types: mime content types to use for body string :type types: list of str """ preferred = 'text/plain' if settings.get( 'prefer_plaintext') else 'text/html' has_preferred = False # see if the mail has our preferred type if types is None: has_preferred = list( typed_subpart_iterator(mail, *preferred.split('/'))) body_parts = [] for part in mail.walk(): ctype = part.get_content_type() if types is not None: if ctype not in types: continue cd = part.get('Content-Disposition', '') if cd.startswith('attachment'): continue # if the mail has our preferred type, we only keep this type # note that if types != None, has_preferred always stays False if has_preferred and ctype != preferred: continue enc = part.get_content_charset() or 'ascii' raw_payload = part.get_payload(decode=True) if ctype == 'text/plain': raw_payload = string_decode(raw_payload, enc) body_parts.append(string_sanitize(raw_payload)) else: # get mime handler key = 'copiousoutput' handler, entry = settings.mailcap_find_match(ctype, key=key) tempfile_name = None stdin = None if entry: handler_raw_commandstring = entry['view'] # in case the mailcap defined command contains no '%s', # we pipe the files content to the handling command via stdin if '%s' in handler_raw_commandstring: # open tempfile, respect mailcaps nametemplate nametemplate = entry.get('nametemplate', '%s') prefix, suffix = parse_mailcap_nametemplate(nametemplate) tmpfile = tempfile.NamedTemporaryFile(delete=False, prefix=prefix, suffix=suffix) # write payload to tmpfile tmpfile.write(raw_payload) tmpfile.close() tempfile_name = tmpfile.name else: stdin = raw_payload # read parameter, create handler command parms = tuple(map('='.join, part.get_params())) # create and call external command cmd = mailcap.subst(entry['view'], ctype, filename=tempfile_name, plist=parms) logging.debug('command: %s' % cmd) logging.debug('parms: %s' % str(parms)) cmdlist = split_commandstring(cmd) # call handler rendered_payload, errmsg, retval = helper.call_cmd(cmdlist, stdin=stdin) # remove tempfile if tempfile_name: os.unlink(tempfile_name) if rendered_payload: # handler had output body_parts.append(string_sanitize(rendered_payload)) return u'\n\n'.join(body_parts)
def apply(self, ui): if self.envelope is None: self.envelope = Envelope() if self.template is not None: # get location of tempsdir, containing msg templates tempdir = settings.get('template_dir') tempdir = os.path.expanduser(tempdir) if not tempdir: xdgdir = os.environ.get('XDG_CONFIG_HOME', os.path.expanduser('~/.config')) tempdir = os.path.join(xdgdir, 'alot', 'templates') path = os.path.expanduser(self.template) if not os.path.dirname(path): # use tempsdir if not os.path.isdir(tempdir): ui.notify('no templates directory: %s' % tempdir, priority='error') return path = os.path.join(tempdir, path) if not os.path.isfile(path): ui.notify('could not find template: %s' % path, priority='error') return try: self.envelope.parse_template(open(path).read()) except Exception as e: ui.notify(str(e), priority='error') return # set forced headers for key, value in self.headers.items(): self.envelope.add(key, value) # set forced headers for separate parameters if self.sender: self.envelope.add('From', self.sender) if self.subject: self.envelope.add('Subject', self.subject) if self.to: self.envelope.add('To', ','.join(self.to)) if self.cc: self.envelope.add('Cc', ','.join(self.cc)) if self.bcc: self.envelope.add('Bcc', ','.join(self.bcc)) # get missing From header if not 'From' in self.envelope.headers: accounts = settings.get_accounts() if len(accounts) == 1: a = accounts[0] fromstring = "%s <%s>" % (a.realname, a.address) self.envelope.add('From', fromstring) else: cmpl = AccountCompleter() fromaddress = yield ui.prompt('From', completer=cmpl, tab=1) if fromaddress is None: raise CommandCanceled() self.envelope.add('From', fromaddress) # add signature if not self.omit_signature: name, addr = email.Utils.parseaddr(self.envelope['From']) account = settings.get_account_by_address(addr) if account is not None: if account.signature: logging.debug('has signature') sig = os.path.expanduser(account.signature) if os.path.isfile(sig): logging.debug('is file') if account.signature_as_attachment: name = account.signature_filename or None self.envelope.attach(sig, filename=name) logging.debug('attached') else: sigcontent = open(sig).read() enc = helper.guess_encoding(sigcontent) mimetype = helper.guess_mimetype(sigcontent) if mimetype.startswith('text'): sigcontent = helper.string_decode(sigcontent, enc) self.envelope.body += '\n' + sigcontent else: ui.notify('could not locate signature: %s' % sig, priority='error') if (yield ui.choice('send without signature?', 'yes', 'no')) == 'no': return # Figure out whether we should GPG sign messages by default # and look up key if so sender = self.envelope.get('From') name, addr = email.Utils.parseaddr(sender) account = settings.get_account_by_address(addr) if account: self.envelope.sign = account.sign_by_default self.envelope.sign_key = account.gpg_key # get missing To header if 'To' not in self.envelope.headers: allbooks = not settings.get('complete_matching_abook_only') logging.debug(allbooks) if account is not None: abooks = settings.get_addressbooks(order=[account], append_remaining=allbooks) logging.debug(abooks) completer = ContactsCompleter(abooks) else: completer = None to = yield ui.prompt('To', completer=completer) if to is None: raise CommandCanceled() self.envelope.add('To', to.strip(' \t\n,')) if settings.get('ask_subject') and \ not 'Subject' in self.envelope.headers: subject = yield ui.prompt('Subject') logging.debug('SUBJECT: "%s"' % subject) if subject is None: raise CommandCanceled() self.envelope.add('Subject', subject) if settings.get('compose_ask_tags'): comp = TagsCompleter(ui.dbman) tagsstring = yield ui.prompt('Tags', completer=comp) tags = filter(lambda x: x, tagsstring.split(',')) if tags is None: raise CommandCanceled() self.envelope.tags = tags if self.attach: for gpath in self.attach: for a in glob.glob(gpath): self.envelope.attach(a) logging.debug('attaching: ' + a) cmd = commands.envelope.EditCommand(envelope=self.envelope, spawn=self.force_spawn, refocus=False) ui.apply_command(cmd)
def _test(self, base, expected, encoding='ascii'): actual = helper.string_decode(base, encoding) self.assertEqual(actual, expected)
def get_tagstring_representation(self, tag, onebelow_normal=None, onebelow_focus=None): """ looks up user's preferred way to represent a given tagstring. :param tag: tagstring :type tag: str :param onebelow_normal: attribute that shines through if unfocussed :type onebelow_normal: urwid.AttrSpec :param onebelow_focus: attribute that shines through if focussed :type onebelow_focus: urwid.AttrSpec If `onebelow_normal` or `onebelow_focus` is given these attributes will be used as fallbacks for fg/bg values '' and 'default'. This returns a dictionary mapping :normal: to :class:`urwid.AttrSpec` used if unfocussed :focussed: to :class:`urwid.AttrSpec` used if focussed :translated: to an alternative string representation """ colourmode = int(self._config.get('colourmode')) theme = self._theme cfg = self._config colours = [1, 16, 256] def colourpick(triple): """ pick attribute from triple (mono,16c,256c) according to current colourmode""" if triple is None: return None return triple[colours.index(colourmode)] # global default attributes for tagstrings. # These could contain values '' and 'default' which we interpret as # "use the values from the widget below" default_normal = theme.get_attribute(colourmode, 'global', 'tag') default_focus = theme.get_attribute(colourmode, 'global', 'tag_focus') # local defaults for tagstring attributes. depend on next lower widget fallback_normal = resolve_att(onebelow_normal, default_normal) fallback_focus = resolve_att(onebelow_focus, default_focus) for sec in cfg['tags'].sections: if re.match('^' + sec + '$', tag): normal = resolve_att(colourpick(cfg['tags'][sec]['normal']), fallback_normal) focus = resolve_att(colourpick(cfg['tags'][sec]['focus']), fallback_focus) translated = cfg['tags'][sec]['translated'] translated = string_decode(translated, 'UTF-8') if translated is None: translated = tag translation = cfg['tags'][sec]['translation'] if translation: translated = re.sub(translation[0], translation[1], tag) break else: normal = fallback_normal focus = fallback_focus translated = tag return {'normal': normal, 'focussed': focus, 'translated': translated}
def apply(self, ui): if self.envelope is None: if self.rest: if self.rest.startswith("mailto"): self.envelope = mailto_to_envelope(self.rest) else: self.envelope = Envelope() self.envelope.add("To", self.rest) else: self.envelope = Envelope() if self.template is not None: # get location of tempsdir, containing msg templates tempdir = settings.get("template_dir") tempdir = os.path.expanduser(tempdir) if not tempdir: xdgdir = os.environ.get("XDG_CONFIG_HOME", os.path.expanduser("~/.config")) tempdir = os.path.join(xdgdir, "alot", "templates") path = os.path.expanduser(self.template) if not os.path.dirname(path): # use tempsdir if not os.path.isdir(tempdir): ui.notify("no templates directory: %s" % tempdir, priority="error") return path = os.path.join(tempdir, path) if not os.path.isfile(path): ui.notify("could not find template: %s" % path, priority="error") return try: self.envelope.parse_template(open(path).read()) except Exception as e: ui.notify(str(e), priority="error") return # set forced headers for key, value in self.headers.items(): self.envelope.add(key, value) # set forced headers for separate parameters if self.sender: self.envelope.add("From", self.sender) if self.subject: self.envelope.add("Subject", self.subject) if self.to: self.envelope.add("To", ",".join(self.to)) if self.cc: self.envelope.add("Cc", ",".join(self.cc)) if self.bcc: self.envelope.add("Bcc", ",".join(self.bcc)) # get missing From header if not "From" in self.envelope.headers: accounts = settings.get_accounts() if len(accounts) == 1: a = accounts[0] fromstring = "%s <%s>" % (a.realname, a.address) self.envelope.add("From", fromstring) else: cmpl = AccountCompleter() fromaddress = yield ui.prompt("From", completer=cmpl, tab=1) if fromaddress is None: raise CommandCanceled() self.envelope.add("From", fromaddress) # add signature if not self.omit_signature: name, addr = email.Utils.parseaddr(self.envelope["From"]) account = settings.get_account_by_address(addr) if account is not None: if account.signature: logging.debug("has signature") sig = os.path.expanduser(account.signature) if os.path.isfile(sig): logging.debug("is file") if account.signature_as_attachment: name = account.signature_filename or None self.envelope.attach(sig, filename=name) logging.debug("attached") else: sigcontent = open(sig).read() enc = helper.guess_encoding(sigcontent) mimetype = helper.guess_mimetype(sigcontent) if mimetype.startswith("text"): sigcontent = helper.string_decode(sigcontent, enc) self.envelope.body += "\n" + sigcontent else: ui.notify("could not locate signature: %s" % sig, priority="error") if (yield ui.choice("send without signature?", "yes", "no")) == "no": return # Figure out whether we should GPG sign messages by default # and look up key if so sender = self.envelope.get("From") name, addr = email.Utils.parseaddr(sender) account = settings.get_account_by_address(addr) if account: self.envelope.sign = account.sign_by_default self.envelope.sign_key = account.gpg_key # get missing To header if "To" not in self.envelope.headers: allbooks = not settings.get("complete_matching_abook_only") logging.debug(allbooks) if account is not None: abooks = settings.get_addressbooks(order=[account], append_remaining=allbooks) logging.debug(abooks) completer = ContactsCompleter(abooks) else: completer = None to = yield ui.prompt("To", completer=completer) if to is None: raise CommandCanceled() self.envelope.add("To", to.strip(" \t\n,")) if settings.get("ask_subject") and not "Subject" in self.envelope.headers: subject = yield ui.prompt("Subject") logging.debug('SUBJECT: "%s"' % subject) if subject is None: raise CommandCanceled() self.envelope.add("Subject", subject) if settings.get("compose_ask_tags"): comp = TagsCompleter(ui.dbman) tagsstring = yield ui.prompt("Tags", completer=comp) tags = filter(lambda x: x, tagsstring.split(",")) if tags is None: raise CommandCanceled() self.envelope.tags = tags if self.attach: for gpath in self.attach: for a in glob.glob(gpath): self.envelope.attach(a) logging.debug("attaching: " + a) cmd = commands.envelope.EditCommand(envelope=self.envelope, spawn=self.force_spawn, refocus=False) ui.apply_command(cmd)