win32mbcs.py
167 lines
| 5.6 KiB
| text/x-python
|
PythonLexer
/ hgext / win32mbcs.py
|
r6887 | # win32mbcs.py -- MBCS filename support for Mercurial | ||
|
r5846 | # | ||
# Copyright (c) 2008 Shun-ichi Goto <shunichi.goto@gmail.com> | ||||
# | ||||
|
r10050 | # Version: 0.3 | ||
|
r5846 | # Author: Shun-ichi Goto <shunichi.goto@gmail.com> | ||
# | ||||
|
r8225 | # This software may be used and distributed according to the terms of the | ||
|
r10263 | # GNU General Public License version 2 or any later version. | ||
|
r5846 | # | ||
|
r8228 | |||
|
r8932 | '''allow the use of MBCS paths with problematic encodings | ||
|
r5846 | |||
|
r8001 | Some MBCS encodings are not good for some path operations (i.e. | ||
splitting path, case conversion, etc.) with its encoded bytes. We call | ||||
such a encoding (i.e. shift_jis and big5) as "problematic encoding". | ||||
This extension can be used to fix the issue with those encodings by | ||||
|
r8665 | wrapping some functions to convert to Unicode string before path | ||
|
r8001 | operation. | ||
|
r5846 | |||
|
r8668 | This extension is useful for: | ||
|
r9216 | |||
- Japanese Windows users using shift_jis encoding. | ||||
- Chinese Windows users using big5 encoding. | ||||
- All users who use a repository with one of problematic encodings on | ||||
case-insensitive file system. | ||||
|
r6887 | |||
This extension is not needed for: | ||||
|
r9216 | |||
- Any user who use only ASCII chars in path. | ||||
- Any user who do not use any of problematic encodings. | ||||
|
r5846 | |||
|
r6887 | Note that there are some limitations on using this extension: | ||
|
r9216 | |||
- You should use single encoding in one repository. | ||||
|
r13067 | - If the repository path ends with 0x5c, .hg/hgrc cannot be read. | ||
|
r13330 | - win32mbcs is not compatible with fixutf8 extension. | ||
|
r10050 | |||
|
r10067 | By default, win32mbcs uses encoding.encoding decided by Mercurial. | ||
You can specify the encoding by config option:: | ||||
|
r6887 | |||
|
r10050 | [win32mbcs] | ||
encoding = sjis | ||||
|
r10067 | It is useful for the users who want to commit with UTF-8 log message. | ||
|
r8894 | ''' | ||
|
r5846 | |||
|
r9098 | import os, sys | ||
|
r5846 | from mercurial.i18n import _ | ||
|
r7948 | from mercurial import util, encoding | ||
|
r16743 | testedwith = 'internal' | ||
|
r5846 | |||
|
r13067 | _encoding = None # see extsetup | ||
|
r10050 | |||
|
r6887 | def decode(arg): | ||
|
r7877 | if isinstance(arg, str): | ||
|
r10050 | uarg = arg.decode(_encoding) | ||
if arg == uarg.encode(_encoding): | ||||
|
r7877 | return uarg | ||
raise UnicodeError("Not local encoding") | ||||
elif isinstance(arg, tuple): | ||||
return tuple(map(decode, arg)) | ||||
elif isinstance(arg, list): | ||||
return map(decode, arg) | ||||
|
r9131 | elif isinstance(arg, dict): | ||
for k, v in arg.items(): | ||||
arg[k] = decode(v) | ||||
|
r7877 | return arg | ||
|
r6887 | |||
def encode(arg): | ||||
|
r7877 | if isinstance(arg, unicode): | ||
|
r10050 | return arg.encode(_encoding) | ||
|
r7877 | elif isinstance(arg, tuple): | ||
return tuple(map(encode, arg)) | ||||
elif isinstance(arg, list): | ||||
return map(encode, arg) | ||||
|
r9131 | elif isinstance(arg, dict): | ||
for k, v in arg.items(): | ||||
arg[k] = encode(v) | ||||
|
r7877 | return arg | ||
|
r6887 | |||
|
r9132 | def appendsep(s): | ||
# ensure the path ends with os.sep, appending it if necessary. | ||||
try: | ||||
us = decode(s) | ||||
except UnicodeError: | ||||
us = s | ||||
if us and us[-1] not in ':/\\': | ||||
s += os.sep | ||||
return s | ||||
|
r9131 | def wrapper(func, args, kwds): | ||
|
r7877 | # check argument is unicode, then call original | ||
for arg in args: | ||||
if isinstance(arg, unicode): | ||||
|
r9131 | return func(*args, **kwds) | ||
|
r5846 | |||
|
r7877 | try: | ||
# convert arguments to unicode, call func, then convert back | ||||
|
r9131 | return encode(func(*decode(args), **decode(kwds))) | ||
|
r7877 | except UnicodeError: | ||
|
r9132 | raise util.Abort(_("[win32mbcs] filename conversion failed with" | ||
|
r10050 | " %s encoding\n") % (_encoding)) | ||
|
r6887 | |||
|
r9132 | def wrapperforlistdir(func, args, kwds): | ||
# Ensure 'path' argument ends with os.sep to avoids | ||||
# misinterpreting last 0x5c of MBCS 2nd byte as path separator. | ||||
if args: | ||||
args = list(args) | ||||
args[0] = appendsep(args[0]) | ||||
|
r9391 | if 'path' in kwds: | ||
|
r9132 | kwds['path'] = appendsep(kwds['path']) | ||
return func(*args, **kwds) | ||||
def wrapname(name, wrapper): | ||||
|
r9098 | module, name = name.rsplit('.', 1) | ||
module = sys.modules[module] | ||||
|
r7877 | func = getattr(module, name) | ||
|
r9131 | def f(*args, **kwds): | ||
return wrapper(func, args, kwds) | ||||
|
r7877 | try: | ||
|
r17428 | f.__name__ = func.__name__ # fails with Python 2.3 | ||
|
r7877 | except Exception: | ||
pass | ||||
setattr(module, name, f) | ||||
|
r6887 | |||
# List of functions to be wrapped. | ||||
# NOTE: os.path.dirname() and os.path.basename() are safe because | ||||
# they use result of os.path.split() | ||||
funcs = '''os.path.join os.path.split os.path.splitext | ||||
|
r15724 | os.path.normpath os.makedirs | ||
|
r9098 | mercurial.util.endswithsep mercurial.util.splitpath mercurial.util.checkcase | ||
|
r14841 | mercurial.util.fspath mercurial.util.pconvert mercurial.util.normpath | ||
mercurial.util.checkwinfilename mercurial.util.checkosfilename''' | ||||
|
r5846 | |||
|
r15724 | # List of Windows specific functions to be wrapped. | ||
winfuncs = '''os.path.splitunc''' | ||||
|
r5846 | # codec and alias names of sjis and big5 to be faked. | ||
|
r6887 | problematic_encodings = '''big5 big5-tw csbig5 big5hkscs big5-hkscs | ||
hkscs cp932 932 ms932 mskanji ms-kanji shift_jis csshiftjis shiftjis | ||||
sjis s_jis shift_jis_2004 shiftjis2004 sjis_2004 sjis2004 | ||||
|
r8714 | shift_jisx0213 shiftjisx0213 sjisx0213 s_jisx0213 950 cp950 ms950 ''' | ||
|
r5846 | |||
|
r13067 | def extsetup(ui): | ||
|
r7877 | # TODO: decide use of config section for this extension | ||
|
r15724 | if ((not os.path.supports_unicode_filenames) and | ||
(sys.platform != 'cygwin')): | ||||
|
r7877 | ui.warn(_("[win32mbcs] cannot activate on this platform.\n")) | ||
return | ||||
|
r10050 | # determine encoding for filename | ||
global _encoding | ||||
_encoding = ui.config('win32mbcs', 'encoding', encoding.encoding) | ||||
|
r7877 | # fake is only for relevant environment. | ||
|
r10050 | if _encoding.lower() in problematic_encodings.split(): | ||
|
r7877 | for f in funcs.split(): | ||
|
r9132 | wrapname(f, wrapper) | ||
|
r15724 | if os.name == 'nt': | ||
for f in winfuncs.split(): | ||||
wrapname(f, wrapper) | ||||
|
r9132 | wrapname("mercurial.osutil.listdir", wrapperforlistdir) | ||
|
r13067 | # Check sys.args manually instead of using ui.debug() because | ||
# command line options is not yet applied when | ||||
# extensions.loadall() is called. | ||||
if '--debug' in sys.argv: | ||||
ui.write("[win32mbcs] activated with encoding: %s\n" | ||||
% _encoding) | ||||