Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 16 additions & 7 deletions Doc/library/curses.ascii.rst
Original file line number Diff line number Diff line change
Expand Up @@ -176,15 +176,18 @@ C library:

Checks for a non-ASCII character (ordinal values 0x80 and above).

These functions accept either integers or single-character strings; when the argument is a
string, it is first converted using the built-in function :func:`ord`.
These functions accept an integer, a single-character string, or a
:class:`curses.complexchar`.
A string is converted using the built-in function :func:`ord`, and a
complexchar by the code of its single character; a complexchar that holds
combining characters is not a single character and matches no class.

Note that all these functions check ordinal bit values derived from the
character of the string you pass in; they do not actually know anything about
the host machine's character encoding.

The following two functions take either a single-character string or integer
byte value; they return a value of the same type.
The following three functions take either a single-character string or an
integer byte value; they return a value of the same type.


.. function:: ascii(c)
Expand All @@ -194,16 +197,22 @@ byte value; they return a value of the same type.

.. function:: ctrl(c)

Return the control character corresponding to the given character (the character
bit value is bitwise-anded with 0x1f).
Return the control character corresponding to the given ASCII character (the
character bit value is bitwise-anded with 0x1f). A non-ASCII character has no
control character and is returned unchanged.

.. versionchanged:: next
A non-ASCII argument is now returned unchanged instead of masked to a
control character.


.. function:: alt(c)

Return the 8-bit character corresponding to the given ASCII character (the
character bit value is bitwise-ored with 0x80).

The following function takes either a single-character string or integer value;
The following function takes a single-character string, an integer value, or a
:class:`curses.complexchar`;
it returns a string.


Expand Down
32 changes: 20 additions & 12 deletions Lib/curses/ascii.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
"""Constants and membership tests for ASCII characters"""

# A character-cell type, present on wide and narrow builds.
from _curses import complexchar as _complexchar

NUL = 0x00 # ^@
SOH = 0x01 # ^A
STX = 0x02 # ^B
Expand Down Expand Up @@ -48,8 +51,12 @@
def _ctoi(c):
if isinstance(c, str):
return ord(c)
else:
return c
if isinstance(c, _complexchar):
# A character cell: its single character, or -1 (matches no class)
# for a cell with combining characters.
s = str(c)
return ord(s) if len(s) == 1 else -1
return c

def isalnum(c): return isalpha(c) or isdigit(c)
def isalpha(c): return isupper(c) or islower(c)
Expand All @@ -69,22 +76,23 @@ def isctrl(c): return 0 <= _ctoi(c) < 32
def ismeta(c): return _ctoi(c) > 127

def ascii(c):
if isinstance(c, str):
return chr(_ctoi(c) & 0x7f)
else:
if isinstance(c, int):
return _ctoi(c) & 0x7f
else:
return chr(_ctoi(c) & 0x7f)

def ctrl(c):
if isinstance(c, str):
return chr(_ctoi(c) & 0x1f)
else:
return _ctoi(c) & 0x1f
code = _ctoi(c)
if not 0 <= code < 128:
# No control character outside ASCII: return c unchanged.
return c if isinstance(c, int) else str(c)
return code & 0x1f if isinstance(c, int) else chr(code & 0x1f)

def alt(c):
if isinstance(c, str):
return chr(_ctoi(c) | 0x80)
else:
if isinstance(c, int):
return _ctoi(c) | 0x80
else:
return chr(_ctoi(c) | 0x80)

def unctrl(c):
bits = _ctoi(c)
Expand Down
35 changes: 35 additions & 0 deletions Lib/test/test_curses.py
Original file line number Diff line number Diff line change
Expand Up @@ -2740,6 +2740,9 @@ def test_ctrl(self):
self.assertEqual(ctrl('\n'), '\n')
self.assertEqual(ctrl('@'), '\0')
self.assertEqual(ctrl(ord('J')), ord('\n'))
# A non-ASCII argument is returned unchanged (no control character).
self.assertEqual(ctrl('\xe9'), '\xe9')
self.assertEqual(ctrl(0xe9), 0xe9)

def test_alt(self):
alt = curses.ascii.alt
Expand All @@ -2764,6 +2767,38 @@ def test_unctrl(self):
self.assertEqual(unctrl(ord('\x8a')), '!^J')
self.assertEqual(unctrl(ord('\xc1')), '!A')

@unittest.skipUnless(hasattr(curses, 'complexchar'),
'requires the curses.complexchar type')
def test_complexchar(self):
# The predicates, ctrl() and unctrl() accept a complexchar too, using
# its single character. A narrow build just forms fewer cells.
cc = curses.complexchar
def storable(s):
try:
cc(s)
except ValueError:
return False
return True

self.assertTrue(curses.ascii.isupper(cc('A')))
self.assertTrue(curses.ascii.isalpha(cc('A', curses.A_BOLD)))
self.assertFalse(curses.ascii.isdigit(cc('A')))
self.assertTrue(curses.ascii.isdigit(cc('7')))
self.assertTrue(curses.ascii.iscntrl(cc('\n')))
self.assertEqual(curses.ascii.ctrl(cc('J')), '\n')
self.assertEqual(curses.ascii.unctrl(cc('\n')), '^J')
self.assertEqual(curses.ascii.unctrl(cc('A')), 'A')
# A non-ASCII character: classified by code point, no control character.
if storable('\xe9'):
self.assertFalse(curses.ascii.isascii(cc('\xe9')))
self.assertTrue(curses.ascii.ismeta(cc('\xe9')))
self.assertEqual(curses.ascii.ctrl(cc('\xe9')), '\xe9')
# A cell with combining marks is not a single character, so no
# predicate matches it (needs a wide build to store).
if storable('e\u0301'):
self.assertFalse(curses.ascii.isalpha(cc('e\u0301')))
self.assertFalse(curses.ascii.isascii(cc('e\u0301')))


def lorem_ipsum(win):
text = [
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
:mod:`curses.ascii` predicates and the :func:`~curses.ascii.ctrl` and
:func:`~curses.ascii.unctrl` functions now accept a :class:`curses.complexchar`.
:func:`~curses.ascii.ctrl` now returns a non-ASCII argument unchanged instead
of masking it to a control character.
Loading