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)
.. 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.
"""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
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)
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)
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
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 = [
--- /dev/null
+: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.