Functional Weave
Code in Python

text.truncate@1.0.0

impl/python.py

2,456 bytes · the Python implementation · view raw

from typing import Any


def _whole(value: Any) -> bool:
    # bool is an int in Python; True is not a length of one here.
    return isinstance(value, int) and not isinstance(value, bool)


def _is_whitespace(ch: str) -> bool:
    """The Unicode White_Space property, spelled out so all three languages
    agree: str.isspace also accepts U+001C-U+001F, which JavaScript and Rust
    do not.
    """
    cp = ord(ch)
    return (
        0x09 <= cp <= 0x0D
        or cp == 0x20
        or cp == 0x85
        or cp == 0xA0
        or cp == 0x1680
        or 0x2000 <= cp <= 0x200A
        or cp == 0x2028
        or cp == 0x2029
        or cp == 0x202F
        or cp == 0x205F
        or cp == 0x3000
    )


def _is_trailing_punctuation(ch: str) -> bool:
    # Punctuation that reads badly directly before an ellipsis.
    return ch in (",", ";", ":", "-", ".")


def truncate(value: str, max_length: int, ellipsis: str) -> str:
    """Shorten ``value`` to at most ``max_length`` code points, ellipsis
    included, cutting at a word boundary where there is one.
    """
    if not isinstance(value, str):
        raise TypeError("truncate needs a string, received %r" % (value,))
    if not _whole(max_length):
        raise TypeError("maxLength must be a whole number, received %r" % (max_length,))
    if max_length < 0:
        raise ValueError("maxLength must be 0 or greater, received %d" % (max_length,))
    if not isinstance(ellipsis, str):
        raise TypeError("ellipsis must be a string, received %r" % (ellipsis,))
    # A Python str is indexed by code point already.
    if len(ellipsis) > max_length:
        raise ValueError(
            "maxLength must be at least the length of the ellipsis, received %d" % (max_length,)
        )
    if len(value) <= max_length:
        return value

    budget = max_length - len(ellipsis)
    end = budget
    # A cut just before whitespace is already a clean boundary.
    if not _is_whitespace(value[budget]):
        last_space = -1
        for i in range(budget - 1, -1, -1):
            if _is_whitespace(value[i]):
                last_space = i
                break
        # No boundary to move back to: one long word is cut hard rather than
        # reduced to a bare ellipsis.
        if last_space > 0:
            end = last_space

    while end > 0 and (_is_whitespace(value[end - 1]) or _is_trailing_punctuation(value[end - 1])):
        end -= 1
    return value[:end] + ellipsis