2009-04-26 03:13:08 +04:00
|
|
|
# encoding.py - character transcoding support for Mercurial
|
|
|
|
#
|
|
|
|
# Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
|
|
|
|
#
|
|
|
|
# This software may be used and distributed according to the terms of the
|
2010-01-20 07:20:08 +03:00
|
|
|
# GNU General Public License version 2 or any later version.
|
2009-04-03 23:51:48 +04:00
|
|
|
|
2009-04-28 19:40:46 +04:00
|
|
|
import error
|
|
|
|
import sys, unicodedata, locale, os
|
2009-04-03 23:51:48 +04:00
|
|
|
|
2010-08-14 03:30:54 +04:00
|
|
|
def _getpreferredencoding():
|
|
|
|
'''
|
|
|
|
On darwin, getpreferredencoding ignores the locale environment and
|
|
|
|
always returns mac-roman. http://bugs.python.org/issue6202 fixes this
|
|
|
|
for Python 2.7 and up. This is the same corrected code for earlier
|
|
|
|
Python versions.
|
|
|
|
|
|
|
|
However, we can't use a version check for this method, as some distributions
|
|
|
|
patch Python to fix this. Instead, we use it as a 'fixer' for the mac-roman
|
|
|
|
encoding, as it is unlikely that this encoding is the actually expected.
|
|
|
|
'''
|
|
|
|
try:
|
|
|
|
locale.CODESET
|
|
|
|
except AttributeError:
|
|
|
|
# Fall back to parsing environment variables :-(
|
|
|
|
return locale.getdefaultlocale()[1]
|
|
|
|
|
|
|
|
oldloc = locale.setlocale(locale.LC_CTYPE)
|
|
|
|
locale.setlocale(locale.LC_CTYPE, "")
|
|
|
|
result = locale.nl_langinfo(locale.CODESET)
|
|
|
|
locale.setlocale(locale.LC_CTYPE, oldloc)
|
|
|
|
|
|
|
|
return result
|
|
|
|
|
|
|
|
_encodingfixers = {
|
|
|
|
'646': lambda: 'ascii',
|
|
|
|
'ANSI_X3.4-1968': lambda: 'ascii',
|
|
|
|
'mac-roman': _getpreferredencoding
|
|
|
|
}
|
2009-04-03 23:51:48 +04:00
|
|
|
|
|
|
|
try:
|
|
|
|
encoding = os.environ.get("HGENCODING")
|
|
|
|
if not encoding:
|
|
|
|
encoding = locale.getpreferredencoding() or 'ascii'
|
2010-08-14 03:30:54 +04:00
|
|
|
encoding = _encodingfixers.get(encoding, lambda: encoding)()
|
2009-04-03 23:51:48 +04:00
|
|
|
except locale.Error:
|
|
|
|
encoding = 'ascii'
|
|
|
|
encodingmode = os.environ.get("HGENCODINGMODE", "strict")
|
|
|
|
fallbackencoding = 'ISO-8859-1'
|
|
|
|
|
|
|
|
def tolocal(s):
|
|
|
|
"""
|
|
|
|
Convert a string from internal UTF-8 to local encoding
|
|
|
|
|
|
|
|
All internal strings should be UTF-8 but some repos before the
|
|
|
|
implementation of locale support may contain latin1 or possibly
|
|
|
|
other character sets. We attempt to decode everything strictly
|
|
|
|
using UTF-8, then Latin-1, and failing that, we use UTF-8 and
|
|
|
|
replace unknown characters.
|
|
|
|
"""
|
|
|
|
for e in ('UTF-8', fallbackencoding):
|
|
|
|
try:
|
|
|
|
u = s.decode(e) # attempt strict decoding
|
|
|
|
return u.encode(encoding, "replace")
|
|
|
|
except LookupError, k:
|
|
|
|
raise error.Abort("%s, please check your locale settings" % k)
|
|
|
|
except UnicodeDecodeError:
|
|
|
|
pass
|
|
|
|
u = s.decode("utf-8", "replace") # last ditch
|
|
|
|
return u.encode(encoding, "replace")
|
|
|
|
|
|
|
|
def fromlocal(s):
|
|
|
|
"""
|
|
|
|
Convert a string from the local character encoding to UTF-8
|
|
|
|
|
|
|
|
We attempt to decode strings using the encoding mode set by
|
|
|
|
HGENCODINGMODE, which defaults to 'strict'. In this mode, unknown
|
|
|
|
characters will cause an error message. Other modes include
|
|
|
|
'replace', which replaces unknown characters with a special
|
|
|
|
Unicode character, and 'ignore', which drops the character.
|
|
|
|
"""
|
|
|
|
try:
|
|
|
|
return s.decode(encoding, encodingmode).encode("utf-8")
|
|
|
|
except UnicodeDecodeError, inst:
|
2010-01-25 09:05:27 +03:00
|
|
|
sub = s[max(0, inst.start - 10):inst.start + 10]
|
2009-04-03 23:51:48 +04:00
|
|
|
raise error.Abort("decoding near '%s': %s!" % (sub, inst))
|
|
|
|
except LookupError, k:
|
|
|
|
raise error.Abort("%s, please check your locale settings" % k)
|
|
|
|
|
|
|
|
def colwidth(s):
|
|
|
|
"Find the column width of a UTF-8 string for display"
|
|
|
|
d = s.decode(encoding, 'replace')
|
|
|
|
if hasattr(unicodedata, 'east_asian_width'):
|
|
|
|
w = unicodedata.east_asian_width
|
replace Python standard textwrap by MBCS sensitive one for i18n text
Mercurial has problem around text wrapping/filling in MBCS encoding
environment, because standard 'textwrap' module of Python can not
treat it correctly. It splits byte sequence for one character into two
lines.
According to unicode specification, "east asian width" classifies
characters into:
W(ide), N(arrow), F(ull-width), H(alf-width), A(mbiguous)
W/N/F/H can be always recognized as 2/1/2/1 bytes in byte sequence,
but 'A' can not. Size of 'A' depends on language in which it is used.
Unicode specification says:
If the context(= language) cannot be established reliably they
should be treated as narrow characters by default
but many of class 'A' characters are full-width, at least, in Japanese
environment.
So, this patch treats class 'A' characters as full-width always for
safety wrapping.
This patch focuses only on MBCS safe-ness, not on writing/printing
rule strict wrapping for each languages
MBCS sensitive textwrap class is originally implemented
by ITO Nobuaki <daydream.trippers@gmail.com>.
2010-06-06 12:20:10 +04:00
|
|
|
return sum([w(c) in 'WFA' and 2 or 1 for c in d])
|
2009-04-03 23:51:48 +04:00
|
|
|
return len(d)
|
|
|
|
|