Package giraph

Grapheme helper package for Python

Sub-modules

giraph.buffer

GraphemeBuffer class module

giraph.constants

Console management constants

giraph.grapheme

Grapheme class module

giraph.logging

Logging

Classes

class Grapheme (char: str = '',
mods: list[str] | None = None,
width: int = 1,
force_wide: bool = False)
Expand source code
class Grapheme:
    """
    Class for storing (potentially clustered) graphemes

    The base character is stored separately from its various modifying
    sequences to accommodate terminals which do not support zero-width
    characters, combining characters, etc. Variation-selected emoji which are
    considered by the terminal (incorrectly) to be narrow graphemes are flagged
    so that the column offset caused during display can be compensated for.
    """

    char: str
    """Base character"""

    mods: list[str]
    """Modifiers"""

    width: int
    """Character width"""

    force_wide: bool
    """Force 'wide' representation"""

    def __init__(
        self,
        char: str = "",
        mods: list[str] | None = None,
        width: int = 1,
        force_wide: bool = False,
    ):
        self.char = char
        self.mods = mods if mods else []
        self.width = width
        self.force_wide = force_wide

    def _modstr(self, s) -> str:
        return "0x%04X" % ord(s) if wcswidth(s) <= 0 else s

    def __eq__(self, __object: object) -> bool:
        if not isinstance(__object, Grapheme):
            return NotImplemented

        return str(self) == str(__object)

    def __repr__(self) -> str:
        return (
            f"Grapheme(char={self.char!r}, "
            f"mods={[self._modstr(c) for c in self.mods]}, "
            f"width={self.width}{' <forced>' if self.force_wide else ''})"
        )

    def __str__(self) -> str:
        return "".join((self.raw, " " if self.force_wide else ""))

    @property
    def raw(self) -> str:
        """The raw output of this grapheme, without forced-width adjustment."""

        return "".join((self.char, "".join(self.mods)))

    @classmethod
    def from_str(self, input: str) -> Grapheme:
        """
        Construct a single `Grapheme` from the given `str`.

        Args:
            input: The input to parse.

        Returns:
            A `Grapheme` instance representing the first grapheme from the
            input.
        """

        # avoid circular import
        from ._from_str import _from_str

        output = _from_str(input, True)
        assert isinstance(output, Grapheme)
        return output

Class for storing (potentially clustered) graphemes

The base character is stored separately from its various modifying sequences to accommodate terminals which do not support zero-width characters, combining characters, etc. Variation-selected emoji which are considered by the terminal (incorrectly) to be narrow graphemes are flagged so that the column offset caused during display can be compensated for.

Class variables

var char : str

Base character

var force_wide : bool

Force 'wide' representation

var mods : list[str]

Modifiers

var width : int

Character width

Static methods

def from_str(input: str) ‑> Grapheme

Construct a single Grapheme from the given str.

Args

input
The input to parse.

Returns

A Grapheme instance representing the first grapheme from the input.

Instance variables

prop raw : str
Expand source code
@property
def raw(self) -> str:
    """The raw output of this grapheme, without forced-width adjustment."""

    return "".join((self.char, "".join(self.mods)))

The raw output of this grapheme, without forced-width adjustment.

class GraphemeBuffer (*args, **kwargs)
Expand source code
class GraphemeBuffer(list[Grapheme | None]):
    """
    A contiguous segment of cells for display on the console, each of which is
    either a `Grapheme` instance or `None`. A value of `None` should denote that
    the cell is occupied by the remainder of the previous `Grapheme` (i.e. the
    previous `Grapheme` has a `width` value greater than 1).
    """

    def __add__(self, __object: str) -> GraphemeBuffer:  # type: ignore
        return GraphemeBuffer(
            super(GraphemeBuffer, self).__add__(
                GraphemeBuffer.from_str(__object)
            )
        )

    def __iadd__(self, __object: str) -> GraphemeBuffer:  # type: ignore
        return super(GraphemeBuffer, self).__iadd__(
            GraphemeBuffer.from_str(__object)
        )

    def __repr__(self) -> str:
        return f"GraphemeBuffer({len(self)})"

    def __setitem__(  # type: ignore
        self,
        __index: SupportsIndex,
        __object: str,
    ):
        return super(GraphemeBuffer, self).__setitem__(
            __index, GraphemeBuffer.from_str(__object)[0]
        )

    def __str__(self):
        return "".join(str(g) if g else "" for g in self)

    def append(self, __object: Grapheme | None) -> None:
        val = super(GraphemeBuffer, self).append(__object)

        # pad wide graphemes
        if __object and __object.width > 1:
            for _ in range(__object.width - 1):
                super(GraphemeBuffer, self).append(None)

        return val

    def insert(self, __index: SupportsIndex, __object: Grapheme | None) -> None:
        if not isinstance(__index, int):
            return NotImplemented

        if __object and __object.width > 1:
            for _ in range(__object.width - 1):
                super(GraphemeBuffer, self).insert(__index, None)

        return super(GraphemeBuffer, self).insert(__index, __object)

    def pop(self, __index: SupportsIndex = -1) -> Grapheme | None:
        if not isinstance(__index, int):
            return NotImplemented

        val = super(GraphemeBuffer, self).pop(__index)
        idx = int(__index)

        if val and val.width > 1 and idx >= 0:
            for _ in range(val.width - 1):
                super(GraphemeBuffer, self).pop(__index)

        iterate = idx > 0

        while not val and idx != 0:
            if iterate:
                idx -= 1

            val = super(GraphemeBuffer, self).pop(idx)

        return val

    @property
    def grapheme_count(self) -> int:
        """The total number of `Grapheme` objects, excluding `None` values."""

        return sum([1 if g else 0 for g in self])

    @property
    def raw(self) -> str:
        """Console output as a `str` without forced-width adjustments."""

        return "".join(g.raw if g else "" for g in self)

    def _strip(self, lstrip: bool = False) -> GraphemeBuffer:
        length = len(self)

        if length == 0:
            return GraphemeBuffer()

        idx = -1 if lstrip else 0
        limit = length + idx
        step = 1 if lstrip else -1
        grapheme: Grapheme | None = None
        discard = set((" ", "\n"))
        absidx = 0

        while (grapheme is None or grapheme.char in discard) and absidx < limit:
            idx += step
            absidx = abs(idx)
            grapheme = self[idx]

        if not grapheme:
            logger.debug("empty after strip")
            return GraphemeBuffer()

        if not lstrip:
            idx -= step * grapheme.width

        if idx == 0:
            return GraphemeBuffer(self.copy())

        logger.debug(f"stripped: {idx}")
        return GraphemeBuffer(self[idx:] if lstrip else self[:idx])

    def lstrip(self) -> GraphemeBuffer:
        """Trim leading spaces/newlines."""

        return self._strip(True)

    def rstrip(self) -> GraphemeBuffer:
        """Trim trailing spaces/newlines."""

        return self._strip()

    def strip(self) -> GraphemeBuffer:
        """Trim leading and trailing spaces/newlines."""

        return self._strip()._strip(True)

    def split(self, separator: Grapheme | str) -> list[GraphemeBuffer]:
        """
        Split the segment into smaller segments, separated by `separator`.

        Args:
            separator: The `Grapheme` or `str` to use for tokenizing.

        Returns:
            A list of `GraphemeBuffer` instances.
        """

        is_grapheme = isinstance(separator, Grapheme)
        result: list[GraphemeBuffer] = []
        segment = GraphemeBuffer()

        for g in [g for g in self if g]:
            if (is_grapheme and g == separator) or (
                not is_grapheme and g.char == separator
            ):
                result.append(segment)
                segment = GraphemeBuffer()
            else:
                segment.append(g)

        if len(segment):
            result.append(segment)

        return result

    @classmethod
    def from_str(cls, input: str) -> GraphemeBuffer:
        """
        Construct a `GraphemeBuffer` from the given `str`.

        Args:
            input: The input to parse.

        Returns:
            A `GraphemeBuffer` instance representing the grapheme(s) from the input.
        """

        # avoid circular import
        from ._from_str import _from_str

        output = _from_str(input, False)
        assert isinstance(output, GraphemeBuffer)
        return output

A contiguous segment of cells for display on the console, each of which is either a Grapheme instance or None. A value of None should denote that the cell is occupied by the remainder of the previous Grapheme (i.e. the previous Grapheme has a width value greater than 1).

Ancestors

  • builtins.list

Static methods

def from_str(input: str) ‑> GraphemeBuffer

Construct a GraphemeBuffer from the given str.

Args

input
The input to parse.

Returns

A GraphemeBuffer instance representing the grapheme(s) from the input.

Instance variables

prop grapheme_count : int
Expand source code
@property
def grapheme_count(self) -> int:
    """The total number of `Grapheme` objects, excluding `None` values."""

    return sum([1 if g else 0 for g in self])

The total number of Grapheme objects, excluding None values.

prop raw : str
Expand source code
@property
def raw(self) -> str:
    """Console output as a `str` without forced-width adjustments."""

    return "".join(g.raw if g else "" for g in self)

Console output as a str without forced-width adjustments.

Methods

def append(self,
_GraphemeBuffer__object: Grapheme | None) ‑> None
Expand source code
def append(self, __object: Grapheme | None) -> None:
    val = super(GraphemeBuffer, self).append(__object)

    # pad wide graphemes
    if __object and __object.width > 1:
        for _ in range(__object.width - 1):
            super(GraphemeBuffer, self).append(None)

    return val

Append object to the end of the list.

def insert(self,
_GraphemeBuffer__index: SupportsIndex,
_GraphemeBuffer__object: Grapheme | None) ‑> None
Expand source code
def insert(self, __index: SupportsIndex, __object: Grapheme | None) -> None:
    if not isinstance(__index, int):
        return NotImplemented

    if __object and __object.width > 1:
        for _ in range(__object.width - 1):
            super(GraphemeBuffer, self).insert(__index, None)

    return super(GraphemeBuffer, self).insert(__index, __object)

Insert object before index.

def lstrip(self) ‑> GraphemeBuffer
Expand source code
def lstrip(self) -> GraphemeBuffer:
    """Trim leading spaces/newlines."""

    return self._strip(True)

Trim leading spaces/newlines.

def pop(self) ‑> Grapheme | None
Expand source code
def pop(self, __index: SupportsIndex = -1) -> Grapheme | None:
    if not isinstance(__index, int):
        return NotImplemented

    val = super(GraphemeBuffer, self).pop(__index)
    idx = int(__index)

    if val and val.width > 1 and idx >= 0:
        for _ in range(val.width - 1):
            super(GraphemeBuffer, self).pop(__index)

    iterate = idx > 0

    while not val and idx != 0:
        if iterate:
            idx -= 1

        val = super(GraphemeBuffer, self).pop(idx)

    return val

Remove and return item at index (default last).

Raises IndexError if list is empty or index is out of range.

def rstrip(self) ‑> GraphemeBuffer
Expand source code
def rstrip(self) -> GraphemeBuffer:
    """Trim trailing spaces/newlines."""

    return self._strip()

Trim trailing spaces/newlines.

def split(self,
separator: Grapheme | str) ‑> list[GraphemeBuffer]
Expand source code
def split(self, separator: Grapheme | str) -> list[GraphemeBuffer]:
    """
    Split the segment into smaller segments, separated by `separator`.

    Args:
        separator: The `Grapheme` or `str` to use for tokenizing.

    Returns:
        A list of `GraphemeBuffer` instances.
    """

    is_grapheme = isinstance(separator, Grapheme)
    result: list[GraphemeBuffer] = []
    segment = GraphemeBuffer()

    for g in [g for g in self if g]:
        if (is_grapheme and g == separator) or (
            not is_grapheme and g.char == separator
        ):
            result.append(segment)
            segment = GraphemeBuffer()
        else:
            segment.append(g)

    if len(segment):
        result.append(segment)

    return result

Split the segment into smaller segments, separated by separator.

Args

separator
The Grapheme or str to use for tokenizing.

Returns

A list of GraphemeBuffer instances.

def strip(self) ‑> GraphemeBuffer
Expand source code
def strip(self) -> GraphemeBuffer:
    """Trim leading and trailing spaces/newlines."""

    return self._strip()._strip(True)

Trim leading and trailing spaces/newlines.