Functional Weave
Code in Python

text.mask@1.0.0

impl/python.py

1,561 bytes · the Python implementation · view raw

from typing import Any, List


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


def _is_separator(ch: str) -> bool:
    # Only these two are separators: they are how card and sort-code numbers
    # are printed.
    return ch == " " or ch == "-"


def mask(value: str, visible: int, mask_char: str, keep_separators: bool) -> str:
    """Mask all but the last ``visible`` characters of ``value``.

    Characters are Unicode code points (a Python str is already indexed by
    code point), so an emoji counts once and the length never changes.
    """
    if not isinstance(value, str):
        raise TypeError("mask needs a string, received %r" % (value,))
    if not _whole(visible):
        raise TypeError("visible must be a whole number, received %r" % (visible,))
    if visible < 0:
        raise ValueError("visible must be 0 or greater, received %d" % (visible,))
    if not isinstance(mask_char, str) or len(mask_char) != 1:
        raise ValueError('maskChar must be exactly one character, received "%s"' % (mask_char,))

    out: List[str] = [""] * len(value)
    shown = 0
    # Walk from the end so "the last n" is counted without a second pass.
    for i in range(len(value) - 1, -1, -1):
        ch = value[i]
        if keep_separators and _is_separator(ch):
            out[i] = ch
        elif shown < visible:
            out[i] = ch
            shown += 1
        else:
            out[i] = mask_char
    return "".join(out)