Claude Code for curses.ascii: Python ASCII Character Classification — Claude Skills 360 Blog
Blog / AI / Claude Code for curses.ascii: Python ASCII Character Classification
AI

Claude Code for curses.ascii: Python ASCII Character Classification

Published: February 4, 2029
Read time: 5 min read
By: Claude Skills 360

Python’s curses.ascii module provides predicates and utilities for testing and converting ASCII characters — a pure-Python complement to the C <ctype.h> functions. from curses import ascii. Predicates: ascii.isalpha(c), ascii.isdigit(c), ascii.isalnum(c), ascii.isspace(c), ascii.isupper(c), ascii.islower(c), ascii.ispunct(c), ascii.isprint(c), ascii.isgraph(c), ascii.iscntrl(c), ascii.isblank(c), ascii.isxdigit(c), ascii.isascii(c), ascii.ismeta(c). All accept either a single-character string or an integer code point. Conversion: ascii.ascii(c) — strip the high bit (mask to 7-bit); ascii.ctrl(c) — return the control character for a letter (ctrl('A')\x01); ascii.alt(c) — set the high bit (alt('a')'\xe1'); ascii.unctrl(c) — printable representation (unctrl('\x01')'^A', unctrl('\n')'^J'). Unlike str.isalpha() and friends, curses.ascii predicates strictly test 7-bit ASCII — they return False for non-ASCII Unicode characters, making them useful for protocol parsing and terminal I/O where strict ASCII semantics are required. Claude Code generates terminal input classifiers, keystroke filters, cursor navigators, byte stream analyzers, and ANSI control sequence parsers.

CLAUDE.md for curses.ascii

## curses.ascii Stack
- Stdlib: from curses import ascii as _ascii
- Predicates (char or int):
-   _ascii.isalpha(c)   _ascii.isdigit(c)  _ascii.isalnum(c)
-   _ascii.isspace(c)   _ascii.isupper(c)  _ascii.islower(c)
-   _ascii.ispunct(c)   _ascii.isprint(c)  _ascii.isgraph(c)
-   _ascii.iscntrl(c)   _ascii.isblank(c)  _ascii.isxdigit(c)
-   _ascii.isascii(c)   _ascii.ismeta(c)
- Conversion:
-   _ascii.ascii(c)   → int (7-bit)
-   _ascii.ctrl(c)    → str control char   ctrl('A') → '\x01'
-   _ascii.alt(c)     → str high-bit char
-   _ascii.unctrl(c)  → str printable repr unctrl('\x01') → '^A'
- Note: strict 7-bit ASCII — returns False for Unicode > 127

curses.ascii Character Classification Pipeline

# app/cursesasciiutil.py — classify, filter, scan, escape, keystroke, decode
from __future__ import annotations

from curses import ascii as _ascii
from dataclasses import dataclass, field
from typing import Iterator


# ─────────────────────────────────────────────────────────────────────────────
# 1. Character classification
# ─────────────────────────────────────────────────────────────────────────────

_CATEGORIES = [
    ("alpha",  _ascii.isalpha),
    ("digit",  _ascii.isdigit),
    ("alnum",  _ascii.isalnum),
    ("space",  _ascii.isspace),
    ("upper",  _ascii.isupper),
    ("lower",  _ascii.islower),
    ("punct",  _ascii.ispunct),
    ("print",  _ascii.isprint),
    ("graph",  _ascii.isgraph),
    ("cntrl",  _ascii.iscntrl),
    ("blank",  _ascii.isblank),
    ("xdigit", _ascii.isxdigit),
    ("ascii",  _ascii.isascii),
    ("meta",   _ascii.ismeta),
]


def classify_char(c: "str | int") -> list[str]:
    """
    Return all category names that apply to character c.

    Example:
        classify_char('A')    # ["alpha", "alnum", "upper", "print", "graph", "ascii"]
        classify_char('\x01') # ["cntrl", "ascii"]
        classify_char(0x80)   # ["cntrl", "meta"]
    """
    return [name for name, fn in _CATEGORIES if fn(c)]


def char_info(c: "str | int") -> dict[str, object]:
    """
    Return a full description dict for a character.

    Example:
        print(char_info('A'))
        print(char_info('\x07'))
    """
    ch = chr(c) if isinstance(c, int) else c
    code = ord(ch)
    return {
        "char":      repr(ch),
        "code":      code,
        "hex":       hex(code),
        "unctrl":    _ascii.unctrl(ch),
        "categories": classify_char(ch),
    }


def ascii_table() -> list[dict[str, object]]:
    """
    Return a list of char_info dicts for all 128 ASCII code points.

    Example:
        for row in ascii_table():
            if "cntrl" in row["categories"]:
                print(row["code"], row["unctrl"])
    """
    return [char_info(i) for i in range(128)]


# ─────────────────────────────────────────────────────────────────────────────
# 2. String filters and scanners
# ─────────────────────────────────────────────────────────────────────────────

def filter_printable(text: str) -> str:
    """
    Remove all non-printable ASCII characters from a string.
    Non-ASCII characters are preserved.

    Example:
        filter_printable("Hello\x07\x00World")   # "HelloWorld"
    """
    return "".join(c for c in text if _ascii.isprint(c) or ord(c) > 127)


def filter_ascii_only(text: str) -> str:
    """
    Remove all characters outside the 7-bit ASCII range.

    Example:
        filter_ascii_only("café résumé")   # "caf rsm"
    """
    return "".join(c for c in text if _ascii.isascii(c))


def find_control_chars(data: "str | bytes") -> list[tuple[int, str]]:
    """
    Find all control characters in a string or bytes, returning (offset, unctrl_repr) pairs.

    Example:
        find_control_chars("line1\r\nline2\x07")
        # [(5, '^M'), (6, '^J'), (11, '^G')]
    """
    results: list[tuple[int, str]] = []
    seq = data if isinstance(data, str) else data.decode("latin-1")
    for i, c in enumerate(seq):
        if _ascii.iscntrl(c):
            results.append((i, _ascii.unctrl(c)))
    return results


def count_categories(text: str) -> dict[str, int]:
    """
    Count characters per ASCII category in a string.

    Example:
        count_categories("Hello, World! 42")
        # {"alpha": 10, "digit": 2, "punct": 2, "space": 2, ...}
    """
    counts: dict[str, int] = {name: 0 for name, _ in _CATEGORIES}
    for c in text:
        for name, fn in _CATEGORIES:
            if fn(c):
                counts[name] += 1
    return {k: v for k, v in counts.items() if v > 0}


# ─────────────────────────────────────────────────────────────────────────────
# 3. Control-character conversions
# ─────────────────────────────────────────────────────────────────────────────

def ctrl_key(letter: str) -> str:
    """
    Return the control character for a letter (Ctrl+A → \\x01, etc.).

    Example:
        ctrl_key('C')   # '\\x03'  (Ctrl+C = SIGINT in many terminals)
        ctrl_key('D')   # '\\x04'  (Ctrl+D = EOF)
        ctrl_key('[')   # '\\x1b'  (Escape)
    """
    return _ascii.ctrl(letter)


def describe_keypress(code: "str | int") -> str:
    """
    Return a human-readable description of a keypress code.

    Example:
        describe_keypress(13)    # "Ctrl+M (CR)"
        describe_keypress(27)    # "Ctrl+[ (ESC)"
        describe_keypress(65)    # "A"
    """
    _NAMES = {
        0: "NUL", 1: "SOH", 2: "STX", 3: "ETX (Ctrl+C)",
        4: "EOT (Ctrl+D)", 7: "BEL", 8: "BS", 9: "HT (Tab)",
        10: "LF (\\n)", 13: "CR (\\r)", 27: "ESC", 32: "Space",
        127: "DEL",
    }
    c = chr(code) if isinstance(code, int) else code
    n = ord(c)
    if n in _NAMES:
        return _NAMES[n]
    if _ascii.iscntrl(c):
        return f"Ctrl+{chr(n + 64)} ({_ascii.unctrl(c)})"
    if _ascii.isprint(c):
        return repr(c)
    return f"0x{n:02x}"


# ─────────────────────────────────────────────────────────────────────────────
# 4. Byte stream scanner
# ─────────────────────────────────────────────────────────────────────────────

@dataclass
class StreamStats:
    total_bytes:    int
    ascii_bytes:    int
    printable_bytes: int
    control_bytes:  int
    meta_bytes:     int
    newlines:       int
    nul_bytes:      int

    @property
    def ascii_ratio(self) -> float:
        return self.ascii_bytes / self.total_bytes if self.total_bytes else 0.0

    @property
    def is_text(self) -> bool:
        return self.ascii_ratio >= 0.95 and self.nul_bytes == 0


def analyze_bytes(data: bytes) -> StreamStats:
    """
    Analyse a byte sequence for ASCII character distribution.

    Example:
        stats = analyze_bytes(open("file.txt", "rb").read())
        print(stats.is_text, stats.ascii_ratio)
    """
    total = len(data)
    ascii_b = printable_b = control_b = meta_b = newlines = nul_b = 0
    for b in data:
        c = chr(b)
        if _ascii.isascii(c):
            ascii_b += 1
        else:
            meta_b += 1
        if _ascii.isprint(c):
            printable_b += 1
        elif _ascii.iscntrl(c):
            control_b += 1
        if b == 10:
            newlines += 1
        if b == 0:
            nul_b += 1
    return StreamStats(
        total_bytes=total,
        ascii_bytes=ascii_b,
        printable_bytes=printable_b,
        control_bytes=control_b,
        meta_bytes=meta_b,
        newlines=newlines,
        nul_bytes=nul_b,
    )


# ─────────────────────────────────────────────────────────────────────────────
# 5. unctrl escape formatter
# ─────────────────────────────────────────────────────────────────────────────

def escape_control(text: str, *, verbose: bool = False) -> str:
    """
    Return a printable representation of text showing control chars as ^X.
    verbose=True uses full names like <NUL>, <CR>, <LF>.

    Example:
        escape_control("line1\r\nline2\x07!")
        # "line1^M^Jline2^G!"
        escape_control("line1\r\nline2\x07!", verbose=True)
        # "line1<CR><LF>line2<BEL>!"
    """
    _VERBOSE = {0: "NUL", 7: "BEL", 8: "BS", 9: "HT",
                10: "LF", 13: "CR", 27: "ESC", 127: "DEL"}
    parts: list[str] = []
    for c in text:
        n = ord(c)
        if _ascii.iscntrl(c):
            if verbose and n in _VERBOSE:
                parts.append(f"<{_VERBOSE[n]}>")
            else:
                parts.append(_ascii.unctrl(c))
        else:
            parts.append(c)
    return "".join(parts)


# ─────────────────────────────────────────────────────────────────────────────
# Demo
# ─────────────────────────────────────────────────────────────────────────────

if __name__ == "__main__":
    print("=== curses.ascii demo ===")

    # ── classify_char ──────────────────────────────────────────────────────
    print("\n--- classify_char ---")
    for sample in ['A', 'z', '5', ' ', '\t', '\n', '\x01', '\x7f', chr(0x80)]:
        cats = classify_char(sample)
        print(f"  {_ascii.unctrl(sample):6s}  {cats}")

    # ── count_categories ───────────────────────────────────────────────────
    print("\n--- count_categories ---")
    msg = "Hello, World! 42\n"
    for cat, n in sorted(count_categories(msg).items()):
        print(f"  {cat:8s}: {n}")

    # ── find_control_chars ─────────────────────────────────────────────────
    print("\n--- find_control_chars ---")
    data = "HTTP/1.0 200 OK\r\nContent-Type: text/plain\r\n\r\nBody"
    for offset, rep in find_control_chars(data):
        print(f"  offset={offset:3d}  {rep!r}")

    # ── describe_keypress ──────────────────────────────────────────────────
    print("\n--- describe_keypress ---")
    for code in [0, 3, 4, 8, 9, 10, 13, 27, 32, 65, 97, 127, 0x80]:
        print(f"  0x{code:02x} ({code:3d}) → {describe_keypress(code)}")

    # ── ctrl_key ───────────────────────────────────────────────────────────
    print("\n--- ctrl_key ---")
    for letter in "ABCDZ[":
        ck = ctrl_key(letter)
        print(f"  Ctrl+{letter} = {_ascii.unctrl(ck):4s}  (0x{ord(ck):02x})")

    # ── escape_control ─────────────────────────────────────────────────────
    print("\n--- escape_control ---")
    raw = "line1\r\nline2\x07end"
    print(f"  compact : {escape_control(raw)!r}")
    print(f"  verbose : {escape_control(raw, verbose=True)!r}")

    # ── analyze_bytes ──────────────────────────────────────────────────────
    print("\n--- analyze_bytes ---")
    stats = analyze_bytes(b"Hello, World!\r\nLine two.\n\x00\x80\xff")
    print(f"  total={stats.total_bytes}  ascii={stats.ascii_bytes}"
          f"  printable={stats.printable_bytes}  control={stats.control_bytes}")
    print(f"  meta={stats.meta_bytes}  newlines={stats.newlines}"
          f"  nul={stats.nul_bytes}  is_text={stats.is_text}")

    print("\n=== done ===")

For the string stdlib companion — string.printable, string.ascii_letters, string.digits, string.punctuation provide pre-built character set strings for membership tests (c in string.ascii_letters) that are slightly more Pythonic for simple checks — use string.* constants and str.isalpha() / str.isdigit() for Unicode-aware classification; use curses.ascii predicates when you need strict 7-bit ASCII semantics (e.g., protocol parsing, terminal I/O where non-ASCII is an error, not a valid character). For the charset-normalizer (PyPI) alternative — charset_normalizer.from_bytes(raw).best().encoding provides full encoding detection including multi-byte charsets and confidence scores for non-ASCII bytestreams — use charset-normalizer (or the chardet alternative) when dealing with text of unknown encoding; use curses.ascii.analyze_bytes() patterns for quick text-vs-binary detection on streams you expect to be ASCII. The Claude Skills 360 bundle includes curses.ascii skill sets covering classify_char()/char_info()/ascii_table() classifiers, filter_printable()/filter_ascii_only()/find_control_chars()/count_categories() string filters, ctrl_key()/describe_keypress() keystroke helpers, StreamStats/analyze_bytes() byte stream analyzer, and escape_control() control character formatter. Start with the free tier to try ASCII classification patterns and curses.ascii pipeline code generation.

Keep Reading

AI

Claude Code for email.contentmanager: Python Email Content Accessors

Read and write EmailMessage body content with Python's email.contentmanager module and Claude Code — email contentmanager ContentManager for the class that maps content types to get and set handler functions allowing EmailMessage to support get_content and set_content with type-specific behaviour, email contentmanager raw_data_manager for the ContentManager instance that handles raw bytes and str payloads without any conversion, email contentmanager content_manager for the standard ContentManager instance used by email.policy.default that intelligently handles text plain text html multipart and binary content types, email contentmanager get_content_text for the handler that returns the decoded text payload of a text-star message part as a str, email contentmanager get_content_binary for the handler that returns the raw decoded bytes payload of a non-text message part, email contentmanager get_data_manager for the get-handler lookup used by EmailMessage get_content to find the right reader function for the content type, email contentmanager set_content text for the handler that creates and sets a text part correctly choosing charset and transfer encoding, email contentmanager set_content bytes for the handler that creates and sets a binary part with base64 encoding and optional filename Content-Disposition, email contentmanager EmailMessage get_content for the method that reads the message body using the registered content manager handlers, email contentmanager EmailMessage set_content for the method that sets the message body and MIME headers in one call, email contentmanager EmailMessage make_alternative make_mixed make_related for the methods that convert a simple message into a multipart container, email contentmanager EmailMessage add_attachment for the method that attaches a file or bytes to a multipart message, and email contentmanager integration with email.message and email.policy and email.mime and io for building high-level email readers attachment extractors text body accessors HTML readers and policy-aware MIME construction pipelines.

5 min read Feb 12, 2029
AI

Claude Code for email.charset: Python Email Charset Encoding

Control header and body encoding for international email with Python's email.charset module and Claude Code — email charset Charset for the class that wraps a character set name with the encoding rules for header encoding and body encoding describing how to encode text for that charset in email messages, email charset Charset header_encoding for the attribute specifying whether headers using this charset should use QP quoted-printable encoding BASE64 encoding or no encoding, email charset Charset body_encoding for the attribute specifying the Content-Transfer-Encoding to use for message bodies in this charset such as QP or BASE64, email charset Charset output_codec for the attribute giving the Python codec name used to encode the string to bytes for the wire format, email charset Charset input_codec for the attribute giving the Python codec name used to decode incoming bytes to str, email charset Charset get_output_charset for returning the output charset name, email charset Charset header_encode for encoding a header string using the charset's header_encoding method, email charset Charset body_encode for encoding body content using the charset's body_encoding, email charset Charset convert for converting a string from the input_codec to the output_codec, email charset add_charset for registering a new charset with custom encoding rules in the global charset registry, email charset add_alias for adding an alias name that maps to an existing registered charset, email charset add_codec for registering a codec name mapping for use by the charset machinery, and email charset integration with email.message and email.mime and email.policy and email.encoders for building international email senders non-ASCII header encoders Content-Transfer-Encoding selectors charset-aware message constructors and MIME encoding pipelines.

5 min read Feb 11, 2029
AI

Claude Code for email.utils: Python Email Address and Header Utilities

Parse and format RFC 2822 email addresses and dates with Python's email.utils module and Claude Code — email utils parseaddr for splitting a display-name plus angle-bracket address string into a realname and email address tuple, email utils formataddr for combining a realname and address string into a properly quoted RFC 2822 address with angle brackets, email utils getaddresses for parsing a list of raw address header strings each potentially containing multiple comma-separated addresses into a list of realname address tuples, email utils parsedate for parsing an RFC 2822 date string into a nine-tuple compatible with time.mktime, email utils parsedate_tz for parsing an RFC 2822 date string into a ten-tuple that includes the UTC offset timezone in seconds, email utils parsedate_to_datetime for parsing an RFC 2822 date string into an aware datetime object with timezone, email utils formatdate for formatting a POSIX timestamp or the current time as an RFC 2822 date string with optional usegmt and localtime flags, email utils format_datetime for formatting a datetime object as an RFC 2822 date string, email utils make_msgid for generating a globally unique Message-ID string with optional idstring and domain components, email utils decode_rfc2231 for decoding an RFC 2231 encoded parameter value into a tuple of charset language and value, email utils encode_rfc2231 for encoding a string as an RFC 2231 encoded parameter value, email utils collapse_rfc2231_value for collapsing a decoded RFC 2231 tuple to a Unicode string, and email utils integration with email.message and email.headerregistry and datetime and time for building address parsers date formatters message-id generators header extractors and RFC-compliant email construction utilities.

5 min read Feb 10, 2029

Put these ideas into practice

Claude Skills 360 gives you production-ready skills for everything in this article — and 2,350+ more. Start free or go all-in.

Back to Blog

Get 360 skills free