Skip to content

Commit 48387e0

Browse files
swidth: use cross platform implementation, fixes #7493
This implementation should be good enough for our usecase (paths) and has no external dependencies. There is also a wcwidth library which might be even better, but would add another dependency.
1 parent 8792a1c commit 48387e0

File tree

5 files changed

+29
-49
lines changed

5 files changed

+29
-49
lines changed

src/borg/platform/__init__.py

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
from .base import ENOATTR, API_VERSION
1212
from .base import SaveFile, sync_dir, fdatasync, safe_fadvise
13-
from .base import get_process_id, fqdn, hostname, hostid
13+
from .base import get_process_id, fqdn, hostname, hostid, swidth
1414

1515
# work around pyinstaller "forgetting" to include the xattr module
1616
from . import xattr # noqa: F401
@@ -24,7 +24,6 @@
2424
from .linux import set_flags, get_flags
2525
from .linux import SyncFile
2626
from .posix import process_alive, local_pid_alive
27-
from .posix import swidth
2827
from .posix import get_errno
2928
from .posix import getosusername
3029
from . import posix_ug as platform_ug
@@ -36,7 +35,6 @@
3635
from .base import get_flags
3736
from .base import SyncFile
3837
from .posix import process_alive, local_pid_alive
39-
from .posix import swidth
4038
from .posix import get_errno
4139
from .posix import getosusername
4240
from . import posix_ug as platform_ug
@@ -47,7 +45,6 @@
4745
from .base import set_flags, get_flags
4846
from .base import SyncFile
4947
from .posix import process_alive, local_pid_alive
50-
from .posix import swidth
5148
from .posix import get_errno
5249
from .posix import getosusername
5350
from . import posix_ug as platform_ug
@@ -60,7 +57,6 @@
6057
from .base import get_flags
6158
from .base import SyncFile
6259
from .posix import process_alive, local_pid_alive
63-
from .posix import swidth
6460
from .posix import get_errno
6561
from .posix import getosusername
6662
from . import posix_ug as platform_ug
@@ -72,7 +68,6 @@
7268
from .base import set_flags, get_flags
7369
from .base import SyncFile
7470
from .posix import process_alive, local_pid_alive
75-
from .posix import swidth
7671
from .posix import get_errno
7772
from .posix import getosusername
7873
from . import posix_ug as platform_ug
@@ -84,7 +79,6 @@
8479
from .base import set_flags, get_flags
8580
from .base import SyncFile
8681
from .windows import process_alive, local_pid_alive
87-
from .base import swidth
8882
from .windows import getosusername
8983
from . import windows_ug as platform_ug
9084

src/borg/platform/base.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import errno
22
import os
33
import socket
4+
import unicodedata
45
import uuid
56
from pathlib import Path
67

@@ -266,7 +267,20 @@ def swidth(s):
266267
267268
For western scripts, this is just len(s), but for cjk glyphs, 2 cells are used.
268269
"""
269-
return len(s)
270+
width = 0
271+
for char in s:
272+
# Get the East Asian Width property
273+
ea_width = unicodedata.east_asian_width(char)
274+
275+
# Wide (W) and Fullwidth (F) characters take 2 cells
276+
if ea_width in ("W", "F"):
277+
width += 2
278+
# Not a zero-width characters (combining marks, format characters)
279+
elif unicodedata.category(char) not in ("Mn", "Me", "Cf"):
280+
# Normal characters take 1 cell
281+
width += 1
282+
283+
return width
270284

271285

272286
# patched socket.getfqdn() - see https://bugs.python.org/issue5004

src/borg/platform/posix.pyx

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -5,34 +5,11 @@ from . import posix_ug
55

66
from libc.errno cimport errno as c_errno
77

8-
from cpython.mem cimport PyMem_Free
9-
from libc.stddef cimport wchar_t
10-
11-
cdef extern from "wchar.h":
12-
# https://www.man7.org/linux/man-pages/man3/wcswidth.3.html
13-
cdef int wcswidth(const wchar_t *s, size_t n)
14-
15-
16-
cdef extern from "Python.h":
17-
# https://docs.python.org/3/c-api/unicode.html#c.PyUnicode_AsWideCharString
18-
wchar_t* PyUnicode_AsWideCharString(object, Py_ssize_t*) except NULL
19-
208

219
def get_errno():
2210
return c_errno
2311

2412

25-
def swidth(s):
26-
cdef Py_ssize_t size
27-
cdef wchar_t *as_wchar = PyUnicode_AsWideCharString(s, &size)
28-
terminal_width = wcswidth(as_wchar, <size_t>size)
29-
PyMem_Free(as_wchar)
30-
if terminal_width >= 0:
31-
return terminal_width
32-
else:
33-
return len(s)
34-
35-
3613
def process_alive(host, pid, thread):
3714
"""
3815
Check whether the (host, pid, thread_id) combination corresponds to a process potentially alive.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
from ...platform import swidth
2+
3+
4+
def test_swidth_ascii():
5+
assert swidth("borg") == 4
6+
7+
8+
def test_swidth_cjk():
9+
assert swidth("バックアップ") == 6 * 2
10+
11+
12+
def test_swidth_mixed():
13+
assert swidth("borgバックアップ") == 4 + 6 * 2

src/borg/testsuite/platform/posix_test.py

Lines changed: 0 additions & 18 deletions
This file was deleted.

0 commit comments

Comments
 (0)