[PATCH 1 of 6] mail: add methods to handle non-ascii chars
Christian Ebert
blacktrash at gmx.net
Sat Jul 12 14:32:48 CDT 2008
# HG changeset patch
# User Christian Ebert <blacktrash at gmx.net>
# Date 1215886319 -3600
# Node ID d1117d4e339364990ed9e1a274add13d8cb7155b
# Parent 2134d6c09432e4e3dbee18d93ec9242a332f7cdc
mail: add methods to handle non-ascii chars
- headencode, addressencode: encode headers
- mimeencode: encode message parts not containing patches
- new email config "charsets"
Users may configure email.charsets as a list of charsets they
consider appropriate for the recipients of their outgoing mails.
By default the list is empty and only us-ascii, util._encoding
are tried; they are always contained in email.charsets.
diff --git a/mercurial/mail.py b/mercurial/mail.py
--- a/mercurial/mail.py
+++ b/mercurial/mail.py
@@ -7,6 +7,7 @@
from i18n import _
import os, smtplib, socket
+import email.Header, email.MIMEText, email.Utils
import util
def _smtp(ui):
@@ -84,3 +85,82 @@
if not util.find_exe(method):
raise util.Abort(_('%r specified as email transport, '
'but not in PATH') % method)
+
+def _charsets(ui):
+ '''obtain charsets to send mail parts not containing patches.'''
+ charsets = [cs for cs in ui.configlist('email', 'charsets')
+ if cs.lower() not in ('ascii', 'us-ascii')]
+ if util._encoding not in charsets:
+ charsets.append(util._encoding)
+ return charsets
+
+def _encode(ui, s, charsets):
+ '''Returns (converted) string, charset tuple.
+ Finds out best charset by cycling through sendcharsets in descending
+ order. Tries both _encoding and _fallbackencoding for input. Only as
+ last resort send as is in fake ascii.
+ Caveat: Do not use for mail parts containing patches!'''
+ try:
+ s.decode('ascii')
+ return s, None
+ except UnicodeDecodeError:
+ sendcharsets = charsets or _charsets(ui)
+
+ def strconv(inputcs):
+ try:
+ u = s.decode(inputcs)
+ except UnicodeDecodeError:
+ raise
+ for cs in sendcharsets:
+ try:
+ return u.encode(cs), cs
+ except UnicodeEncodeError:
+ pass
+ except LookupError:
+ ui.warn(_('ignoring invalid sendcharset: %s\n') % cs)
+ # if all conversion attempts fail, send broken ascii
+ return s, None
+
+ try:
+ s, scs = strconv(util._encoding)
+ except UnicodeDecodeError:
+ try:
+ s, scs = strconv(util._fallbackencoding)
+ except UnicodeDecodeError:
+ scs = None
+ return s, scs
+
+def headencode(ui, s, charsets=None):
+ '''returns RFC-2047 compliant header from given string.'''
+ # split into words?
+ s, cs = _encode(ui, s, charsets)
+ if cs:
+ return str(email.Header.Header(s, cs))
+ return s
+
+def addressencode(ui, address, charsets=None):
+ '''turn address into RFC-2047 compliant header.'''
+ if not address:
+ return ''
+ name, addr = email.Utils.parseaddr(address)
+ name = headencode(ui, name, charsets)
+ try:
+ acc, dom = addr.split('@')
+ acc = acc.encode('ascii')
+ dom = dom.encode('idna')
+ addr = '%s@%s' % (acc, dom)
+ except UnicodeDecodeError:
+ raise util.Abort(_('invalid email address: %s') % addr)
+ except ValueError:
+ try:
+ # too strict?
+ addr = addr.encode('ascii')
+ except UnicodeDecodeError:
+ raise util.Abort(_('invalid local address: %s') % addr)
+ return email.Utils.formataddr((name, addr))
+
+def mimeencode(ui, s, charsets=None):
+ '''creates mime text object, encodes it if needed, and sets
+ charset and transfer-encoding accordingly.'''
+ s, cs = _encode(ui, s, charsets)
+ return email.MIMEText.MIMEText(s, 'plain', cs or 'us-ascii')
More information about the Mercurial-devel
mailing list