2,699 bytes · the Python implementation · view raw
from typing import Any, Optional
from .form_character_count_types import CharacterCount, CountUnit
def _whole(value: Any) -> bool:
# bool is an int in Python; True is not a limit of one here.return isinstance(value, int) andnot 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 <= 0x0Dor cp == 0x20or cp == 0x85or cp == 0xA0or cp == 0x1680or0x2000 <= cp <= 0x200Aor cp == 0x2028or cp == 0x2029or cp == 0x202For cp == 0x205For cp == 0x3000
)
def _count_words(text: str) -> int:
words = 0
in_word = Falsefor ch in text:
if _is_whitespace(ch):
in_word = Falseelifnot in_word:
in_word = True
words += 1return words
def character_count(text: str, limit: int, unit: CountUnit, threshold: Optional[int]) -> CharacterCount:
"""GOV.UK's character count: how many characters (code points) or words have been typed against a limit, and the sentence to show under the box. """ifnot isinstance(text, str):
raise TypeError("text must be a string")
ifnot _whole(limit) or limit < 1:
raise ValueError("limit must be a whole number of at least 1, received %r" % (limit,))
if unit notin ("characters", "words"):
raise ValueError("unit must be characters or words, received %r" % (unit,))
if threshold isnotNoneand (not _whole(threshold) or threshold < 0or threshold > 100):
raise ValueError("threshold must be a whole number from 0 to 100, received %r" % (threshold,))
# A Python str is indexed by code point already.
count = _count_words(text) if unit == "words"else len(text)
remaining = limit - count
over = remaining < 0# count >= limit * threshold / 100, kept in whole numbers so 50% of 3 is 1.5, not 1.
visible = over or threshold isNoneor count * 100 >= limit * threshold
one = "word"if unit == "words"else"character"def noun(n: int) -> str:
return one if n == 1else unit
if over:
message = "You have %d %s too many" % (-remaining, noun(-remaining))
else:
message = "You have %d %s remaining" % (remaining, noun(remaining))
return CharacterCount(
count=count,
remaining=remaining,
over=over,
visible=visible,
message=message,
description="You can enter up to %d %s" % (limit, noun(limit)),
)