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) andnot 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. """ifnot isinstance(value, str):
raise TypeError("mask needs a string, received %r" % (value,))
ifnot _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,))
ifnot 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 += 1else:
out[i] = mask_char
return"".join(out)