Source code for domdf_python_tools.words

#!/usr/bin/env python
#
#  words.py
"""
Functions for working with (English) words.

.. versionadded:: 0.4.5
"""
#
#  Copyright © 2020-2022 Dominic Davis-Foster <dominic@davis-foster.co.uk>
#
#  Permission is hereby granted, free of charge, to any person obtaining a copy
#  of this software and associated documentation files (the "Software"), to deal
#  in the Software without restriction, including without limitation the rights
#  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
#  copies of the Software, and to permit persons to whom the Software is
#  furnished to do so, subject to the following conditions:
#
#  The above copyright notice and this permission notice shall be included in all
#  copies or substantial portions of the Software.
#
#  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
#  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
#  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
#  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
#  DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
#  OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
#  OR OTHER DEALINGS IN THE SOFTWARE.
#
#  List of 10000 English words from https://github.com/first20hours/google-10000-english/
#  Derived from the Google Web Trillion Word Corpus,
#      as described by Thorsten Brants and Alex Franz,
#      and distributed by the Linguistic Data Consortium.
#
#  Subsets of this corpus distributed by Peter Novig.
#  Corpus editing and cleanup by Josh Kaufman.
#

# stdlib
import functools
import random
import re
import sys
from gettext import ngettext
from reprlib import recursive_repr
from string import ascii_lowercase, ascii_uppercase
from typing import Any, Dict, Iterable, List, NamedTuple, Optional, Tuple

# this package
import domdf_python_tools
from domdf_python_tools.compat import PYPY
from domdf_python_tools.doctools import prettify_docstrings

__all__ = [
		"greek_uppercase",
		"greek_lowercase",
		"get_words_list",
		"get_random_word",
		"make_font",
		"Font",
		"SERIF_BOLD_LETTERS",
		"SERIF_ITALIC_LETTERS",
		"SERIF_BOLD_ITALIC_LETTERS",
		"SANS_SERIF_LETTERS",
		"SANS_SERIF_BOLD_LETTERS",
		"SANS_SERIF_ITALIC_LETTERS",
		"SANS_SERIF_BOLD_ITALIC_LETTERS",
		"SCRIPT_LETTERS",
		"FRAKTUR_LETTERS",
		"MONOSPACE_LETTERS",
		"DOUBLESTRUCK_LETTERS",
		"alpha_sort",
		"as_text",
		"word_join",
		"TAB",
		"CR",
		"LF",
		"Plural",
		"PluralPhrase",
		"truncate_string",
		]

ascii_digits = "0123456789"
"""
ASCII numbers.

.. versionadded:: 0.7.0
"""

greek_uppercase = "ΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡϴΣΤΥΦΧΨΩ"
"""
Uppercase Greek letters.

.. versionadded:: 0.7.0
"""

greek_lowercase = "αβγδεζηθικλμνξοπρςστυφχψω∂ϵϑϰϕϱϖ"
"""
Lowercase Greek letters.

.. versionadded:: 0.7.0
"""


[docs]@functools.lru_cache() def get_words_list(min_length: int = 0, max_length: int = -1) -> List[str]: """ Returns the list of words, optionally only those whose length is between ``min_length`` and ``max_length``. .. versionadded:: 0.4.5 :param min_length: The minimum length of the words to return :param max_length: The maximum length of the words to return. A value of ``-1`` indicates no upper limit. :no-default max_length: :return: The list of words meeting the above specifiers. """ # noqa: D400 # this package from domdf_python_tools.compat import importlib_resources words: str = importlib_resources.read_text("domdf_python_tools", "google-10000-english-no-swears.txt") words_list: List[str] = words.splitlines() if min_length > 0 or max_length != -1: if max_length == -1: words_list = [word for word in words_list if min_length <= len(word)] else: words_list = [word for word in words_list if min_length <= len(word) <= max_length] return words_list
[docs]def get_random_word(min_length: int = 0, max_length: int = -1) -> str: """ Returns a random word, optionally only one whose length is between ``min_length`` and ``max_length``. .. versionadded:: 0.4.5 :param min_length: The minimum length of the words to return :param max_length: The maximum length of the words to return. A value of ``-1`` indicates no upper limit. :no-default max_length: :return: A random word meeting the above specifiers. """ # noqa: D400 words_list = get_words_list(min_length=min_length, max_length=max_length) return random.choice(words_list)
# _default_unicode_sort_order: str = "".join(sorted(chr(i) for i in range(sys.maxunicode + 1)))
[docs]def alpha_sort( iterable: Iterable[str], alphabet: Iterable[str], # = _default_unicode_sort_order reverse: bool = False, ) -> List[str]: """ Sorts a list of strings using a custom alphabet. .. versionadded:: 0.7.0 :param iterable: The strings to sort. :param alphabet: The custom alphabet to use for sorting. :param reverse: """ alphabet_ = list(alphabet) try: return sorted(iterable, key=lambda attr: [alphabet_.index(letter) for letter in attr], reverse=reverse) except ValueError as e: m = re.match(r"'(.*)' is not in list", str(e)) if m: raise ValueError(f"The character {m.group(1)!r} was not found in the alphabet.") from None else: # pragma: no cover raise e
[docs]class Font(Dict[str, str]): """ Represents a Unicode pseudo-font. Mapping of ASCII letters to their equivalents in the pseudo-font. Individual characters can be converted using the :meth:`Font.get <domdf_python_tools.words.Font.get>` method or the ``getitem`` syntax. Entire strings can be converted by calling the :class:`~domdf_python_tools.words.Font` object and passing the string as the first argument. """
[docs] def __getitem__(self, char: str) -> str: """ Returns the given character in this font. If the character is not found in this font the character is returned unchanged. :param char: The character to convert. """ char = str(char) if char not in self: return str(char) else: return str(super().__getitem__(char))
[docs] def __call__(self, text: str) -> str: """ Returns the given text in this font. :param text: """ return ''.join(self[char] for char in text)
[docs] def get(self, char: str, default: Optional[str] = None) -> str: # type: ignore """ Returns the given character in this font. If the character is not found in this font the character is returned unchanged or, if a value for ``default`` is provided, that is returned instead. :param char: The character to convert. :param default: Optional default value. """ if char not in self and default is not None: return str(default) else: return self[char]
[docs]def make_font( uppers: Iterable[str], lowers: Iterable[str], digits: Optional[Iterable[str]] = None, greek_uppers: Optional[Iterable[str]] = None, greek_lowers: Optional[Iterable[str]] = None, ) -> Font: """ Returns a dictionary mapping ASCII alphabetical characters and digits to the Unicode equivalents in a different pseudo-font. .. versionadded:: 0.7.0 :param uppers: Iterable of uppercase letters (A-Z, 26 characters). :param lowers: Iterable of lowercase letters (a-z, 26 characters). :param digits: Optional iterable of digits (0-9). :param greek_uppers: Optional iterable of uppercase Greek letters (A-Ω, 25 characters). :param greek_lowers: Optional iterable of lowercase Greek letters (α-ϖ, 32 characters). """ # noqa: D400 font = Font({ **dict(zip(ascii_uppercase, uppers)), **dict(zip(ascii_lowercase, lowers)), }) if digits: font.update({char: unichar for char, unichar in zip(ascii_digits, digits)}) if greek_uppers: font.update({char: unichar for char, unichar in zip(greek_uppercase, greek_uppers)}) if greek_lowers: font.update({char: unichar for char, unichar in zip(greek_lowercase, greek_lowers)}) return font
#: Bold Serif letters (uppercase) SERIF_BOLD_UPPER = "𝐀𝐁𝐂𝐃𝐄𝐅𝐆𝐇𝐈𝐉𝐊𝐋𝐌𝐍𝐎𝐏𝐐𝐑𝐒𝐓𝐔𝐕𝐖𝐗𝐘𝐙" #: Bold Serif letters (lowercase) SERIF_BOLD_LOWER = "𝐚𝐛𝐜𝐝𝐞𝐟𝐠𝐡𝐢𝐣𝐤𝐥𝐦𝐧𝐨𝐩𝐪𝐫𝐬𝐭𝐮𝐯𝐰𝐱𝐲𝐳" #: Bold Serif digits SERIF_BOLD_DIGITS = "𝟎𝟏𝟐𝟑𝟒𝟓𝟔𝟕𝟖𝟗" #: Bold Serif Greek letters (uppercase) SERIF_BOLD_GREEK_UPPER = "𝚨𝚩𝚪𝚫𝚬𝚭𝚮𝚯𝚰𝚱𝚲𝚳𝚴𝚵𝚶𝚷𝚸𝚹𝚺𝚻𝚼𝚽𝚾𝚿𝛀" #: Bold Serif Greek letters (lowercase) SERIF_BOLD_GREEK_LOWER = "𝛂𝛃𝛄𝛅𝛆𝛇𝛈𝛉𝛊𝛋𝛌𝛍𝛎𝛏𝛐𝛑𝛒𝛓𝛔𝛕𝛖𝛗𝛘𝛙𝛚𝛛𝛜𝛝𝛞𝛟𝛠𝛡" SERIF_BOLD_LETTERS = make_font( uppers=SERIF_BOLD_UPPER, lowers=SERIF_BOLD_LOWER, digits=SERIF_BOLD_DIGITS, greek_uppers=SERIF_BOLD_GREEK_UPPER, greek_lowers=SERIF_BOLD_GREEK_LOWER, ) """ Bold Serif :class:`~domdf_python_tools.words.Font`. This font includes numbers and Greek letters. .. versionadded:: 0.7.0 """ #: Italic Serif letters (uppercase) SERIF_ITALIC_UPPER = "𝐴𝐵𝐶𝐷𝐸𝐹𝐺𝐻𝐼𝐽𝐾𝐿𝑀𝑁𝑂𝑃𝑄𝑅𝑆𝑇𝑈𝑉𝑊𝑋𝑌𝑍" #: Italic Serif letters (lowercase) SERIF_ITALIC_LOWER = "𝑎𝑏𝑐𝑑𝑒𝑓𝑔ℎ𝑖𝑗𝑘𝑙𝑚𝑛𝑜𝑝𝑞𝑟𝑠𝑡𝑢𝑣𝑤𝑥𝑦𝑧" #: Italic Serif Greek letters (uppercase) SERIF_ITALIC_GREEK_UPPER = "𝛢𝛣𝛤𝛥𝛦𝛧𝛨𝛩𝛪𝛫𝛬𝛭𝛮𝛯𝛰𝛱𝛲𝛳𝛴𝛵𝛶𝛷𝛸𝛹𝛺𝛻" #: Italic Serif Greek letters (lowercase) SERIF_ITALIC_GREEK_LOWER = "𝛼𝛽𝛾𝛿𝜀𝜁𝜂𝜃𝜄𝜅𝜆𝜇𝜈𝜉𝜊𝜋𝜌𝜍𝜎𝜏𝜐𝜑𝜒𝜓𝜔𝜕𝜖𝜗𝜘𝜙𝜚𝜛" SERIF_ITALIC_LETTERS = make_font( uppers=SERIF_ITALIC_UPPER, lowers=SERIF_ITALIC_LOWER, greek_uppers=SERIF_ITALIC_GREEK_UPPER, greek_lowers=SERIF_ITALIC_GREEK_LOWER, ) """ Italic Serif :class:`~domdf_python_tools.words.Font`. This font includes Greek letters. .. versionadded:: 0.7.0 """ #: Bold and Italic Serif letters (uppercase) SERIF_BOLD_ITALIC_UPPER = "𝑨𝑩𝑪𝑫𝑬𝑭𝑮𝑯𝑰𝑱𝑲𝑳𝑴𝑵𝑶𝑷𝑸𝑹𝑺𝑻𝑼𝑽𝑾𝑿𝒀𝒁" #: Bold and Italic Serif letters (lowercase) SERIF_BOLD_ITALIC_LOWER = "𝒂𝒃𝒄𝒅𝒆𝒇𝒈𝒉𝒊𝒋𝒌𝒍𝒎𝒏𝒐𝒑𝒒𝒓𝒔𝒕𝒖𝒗𝒘𝒙𝒚𝒛" #: Bold and Italic Serif Greek letters (uppercase) SERIF_BOLD_ITALIC_GREEK_UPPER = "𝜜𝜝𝜞𝜟𝜠𝜡𝜢𝜣𝜤𝜥𝜦𝜧𝜨𝜩𝜪𝜫𝜬𝜭𝜮𝜯𝜰𝜱𝜲𝜳𝜴𝜵" #: Bold and Italic Serif Greek letters (lowercase) SERIF_BOLD_ITALIC_GREEK_LOWER = "𝜶𝜷𝜸𝜹𝜺𝜻𝜼𝜽𝜾𝜿𝝀𝝁𝝂𝝃𝝄𝝅𝝆𝝇𝝈𝝉𝝊𝝋𝝌𝝍𝝎𝝏𝝐𝝑𝝒𝝓𝝔𝝕" SERIF_BOLD_ITALIC_LETTERS = make_font( uppers=SERIF_BOLD_ITALIC_UPPER, lowers=SERIF_BOLD_ITALIC_LOWER, greek_uppers=SERIF_BOLD_ITALIC_GREEK_UPPER, greek_lowers=SERIF_BOLD_ITALIC_GREEK_LOWER, ) """ Bold and Italic Serif :class:`~domdf_python_tools.words.Font`. This font includes Greek letters. .. versionadded:: 0.7.0 """ #: Normal Sans-Serif letters (uppercase) SANS_SERIF_UPPER = "𝖠𝖡𝖢𝖣𝖤𝖥𝖦𝖧𝖨𝖩𝖪𝖫𝖬𝖭𝖮𝖯𝖰𝖱𝖲𝖳𝖴𝖵𝖶𝖷𝖸𝖹" #: Normal Sans-Serif letters (lowercase) SANS_SERIF_LOWER = "𝖺𝖻𝖼𝖽𝖾𝖿𝗀𝗁𝗂𝗃𝗄𝗅𝗆𝗇𝗈𝗉𝗊𝗋𝗌𝗍𝗎𝗏𝗐𝗑𝗒𝗓" #: Normal Sans-Serif digits SANS_SERIF_DIGITS = "𝟢𝟣𝟤𝟥𝟦𝟧𝟨𝟩𝟪𝟫" SANS_SERIF_LETTERS = make_font( uppers=SANS_SERIF_UPPER, lowers=SANS_SERIF_LOWER, digits=SANS_SERIF_DIGITS, ) """ Normal Sans-Serif :class:`~domdf_python_tools.words.Font`. This font includes numbers. .. versionadded:: 0.7.0 """ #: Bold Sans-Serif letters (uppercase) SANS_SERIF_BOLD_UPPER = "𝗔𝗕𝗖𝗗𝗘𝗙𝗚𝗛𝗜𝗝𝗞𝗟𝗠𝗡𝗢𝗣𝗤𝗥𝗦𝗧𝗨𝗩𝗪𝗫𝗬𝗭" #: Bold Sans-Serif letters (lowercase) SANS_SERIF_BOLD_LOWER = "𝗮𝗯𝗰𝗱𝗲𝗳𝗴𝗵𝗶𝗷𝗸𝗹𝗺𝗻𝗼𝗽𝗾𝗿𝘀𝘁𝘂𝘃𝘄𝘅𝘆𝘇" #: Bold Sans-Serif digits SANS_SERIF_BOLD_DIGITS = "𝟬𝟭𝟮𝟯𝟰𝟱𝟲𝟳𝟴𝟵" SANS_SERIF_BOLD_LETTERS = make_font( uppers=SANS_SERIF_BOLD_UPPER, lowers=SANS_SERIF_BOLD_LOWER, digits=SANS_SERIF_BOLD_DIGITS, ) """ Bold Sans-Serif :class:`~domdf_python_tools.words.Font`. This font includes numbers. .. versionadded:: 0.7.0 """ #: Italic Sans-Serif letters (uppercase) SANS_SERIF_ITALIC_UPPER = "𝘈𝘉𝘊𝘋𝘌𝘍𝘎𝘏𝘐𝘑𝘒𝘓𝘔𝘕𝘖𝘗𝘘𝘙𝘚𝘛𝘜𝘝𝘞𝘟𝘠𝘡" #: Italic Sans-Serif letters (lowercase) SANS_SERIF_ITALIC_LOWER = "𝘢𝘣𝘤𝘥𝘦𝘧𝘨𝘩𝘪𝘫𝘬𝘭𝘮𝘯𝘰𝘱𝘲𝘳𝘴𝘵𝘶𝘷𝘸𝘹𝘺𝘻" SANS_SERIF_ITALIC_LETTERS = make_font( uppers=SANS_SERIF_ITALIC_UPPER, lowers=SANS_SERIF_ITALIC_LOWER, ) """ Italic Sans-Serif :class:`~domdf_python_tools.words.Font`. .. versionadded:: 0.7.0 """ #: Bold and Italic Sans-Serif letters (uppercase) SANS_SERIF_BOLD_ITALIC_UPPER = "𝘼𝘽𝘾𝘿𝙀𝙁𝙂𝙃𝙄𝙅𝙆𝙇𝙈𝙉𝙊𝙋𝙌𝙍𝙎𝙏𝙐𝙑𝙒𝙓𝙔𝙕" #: Bold and Italic Sans-Serif letters (lowercase) SANS_SERIF_BOLD_ITALIC_LOWER = "𝙖𝙗𝙘𝙙𝙚𝙛𝙜𝙝𝙞𝙟𝙠𝙡𝙢𝙣𝙤𝙥𝙦𝙧𝙨𝙩𝙪𝙫𝙬𝙭𝙮𝙯" #: Bold and Italic Sans-Serif letters (uppercase) SANS_SERIF_BOLD_ITALIC_GREEK_UPPER = "𝞐𝞑𝞒𝞓𝞔𝞕𝞖𝞗𝞘𝞙𝞚𝞛𝞜𝞝𝞞𝞟𝞠𝞡𝞢𝞣𝞤𝞥𝞦𝞧𝞨𝞩" #: Bold and Italic Sans-Serif letters (lowercase) SANS_SERIF_BOLD_ITALIC_GREEK_LOWER = "𝞪𝞫𝞬𝞭𝞮𝞯𝞰𝞱𝞲𝞳𝞴𝞵𝞶𝞷𝞸𝞹𝞺𝞻𝞼𝞽𝞾𝞿𝟀𝟁𝟂𝟃𝟄𝟅𝟆𝟇𝟈𝟉" SANS_SERIF_BOLD_ITALIC_LETTERS = make_font( uppers=SANS_SERIF_BOLD_ITALIC_UPPER, lowers=SANS_SERIF_BOLD_ITALIC_LOWER, greek_uppers=SANS_SERIF_BOLD_ITALIC_GREEK_UPPER, greek_lowers=SANS_SERIF_BOLD_ITALIC_GREEK_LOWER, ) """ Bold and Italic Sans-Serif :class:`~domdf_python_tools.words.Font`. This font includes Greek letters. .. versionadded:: 0.7.0 """ #: Script letters (uppercase) SCRIPT_UPPER = "𝓐𝓑𝓒𝓓𝓔𝓕𝓖𝓗𝓘𝓙𝓚𝓛𝓜𝓝𝓞𝓟𝓠𝓡𝓢𝓣𝓤𝓥𝓦𝓧𝓨𝓩" #: Script letters (lowercase) SCRIPT_LOWER = "𝓪𝓫𝓬𝓭𝓮𝓯𝓰𝓱𝓲𝓳𝓴𝓵𝓶𝓷𝓸𝓹𝓺𝓻𝓼𝓽𝓾𝓿𝔀𝔁𝔂𝔃" SCRIPT_LETTERS = make_font(SCRIPT_UPPER, SCRIPT_LOWER) """ Script :class:`~domdf_python_tools.words.Font`. .. versionadded:: 0.7.0 """ #: Fraktur letters (uppercase) FRAKTUR_UPPER = "𝕬𝕭𝕮𝕯𝕰𝕱𝕲𝕳𝕴𝕵𝕶𝕷𝕸𝕹𝕺𝕻𝕼𝕽𝕾𝕿𝖀𝖁𝖂𝖃𝖄𝖅" #: Fraktur letters (lowercase) FRAKTUR_LOWER = "𝖆𝖇𝖈𝖉𝖊𝖋𝖌𝖍𝖎𝖏𝖐𝖑𝖒𝖓𝖔𝖕𝖖𝖗𝖘𝖙𝖚𝖛𝖜𝖝𝖞𝖟" FRAKTUR_LETTERS = make_font(FRAKTUR_UPPER, FRAKTUR_LOWER) """ Fraktur :class:`~domdf_python_tools.words.Font`. .. versionadded:: 0.7.0 """ #: Monospace letters (uppercase) MONOSPACE_UPPER = "𝙰𝙱𝙲𝙳𝙴𝙵𝙶𝙷𝙸𝙹𝙺𝙻𝙼𝙽𝙾𝙿𝚀𝚁𝚂𝚃𝚄𝚅𝚆𝚇𝚈𝚉" #: Monospace letters (lowercase) MONOSPACE_LOWER = "𝚊𝚋𝚌𝚍𝚎𝚏𝚐𝚑𝚒𝚓𝚔𝚕𝚖𝚗𝚘𝚙𝚚𝚛𝚜𝚝𝚞𝚟𝚠𝚡𝚢𝚣" #: Monospace digits MONOSPACE_DIGITS = "𝟶𝟷𝟸𝟹𝟺𝟻𝟼𝟽𝟾𝟿" MONOSPACE_LETTERS = make_font(MONOSPACE_UPPER, MONOSPACE_LOWER, MONOSPACE_DIGITS) """ Monospace :class:`~domdf_python_tools.words.Font`. This font includes numbers. .. versionadded:: 0.7.0 """ #: Doublestruck letters (uppercase) DOUBLESTRUCK_UPPER = "𝔸𝔹ℂ𝔻𝔼𝔽𝔾ℍ𝕀𝕁𝕂𝕃𝕄ℕ𝕆ℙℚℝ𝕊𝕋𝕌𝕍𝕎𝕏𝕐ℤ" #: Doublestruck letters (lowercase) DOUBLESTRUCK_LOWER = "𝕒𝕓𝕔𝕕𝕖𝕗𝕘𝕙𝕚𝕛𝕜𝕝𝕞𝕟𝕠𝕡𝕢𝕣𝕤𝕥𝕦𝕧𝕨𝕩𝕪𝕫" #: Doublestruck digits DOUBLESTRUCK_DIGITS = "𝟘𝟙𝟚𝟛𝟜𝟝𝟞𝟟𝟠𝟡" DOUBLESTRUCK_LETTERS = make_font(DOUBLESTRUCK_UPPER, DOUBLESTRUCK_LOWER, DOUBLESTRUCK_DIGITS) """ Doublestruck :class:`~domdf_python_tools.words.Font`. This font includes numbers. .. versionadded:: 0.7.0 """
[docs]def as_text(value: Any) -> str: """ Convert the given value to a string. :py:obj:`None` is converted to ``''``. :param value: The value to convert to a string. :rtype: .. versionchanged:: 0.8.0 Moved from :mod:`domdf_python_tools.utils`. """ if value is None: return '' return str(value)
[docs]def word_join( iterable: Iterable[str], use_repr: bool = False, oxford: bool = False, delimiter: str = ',', connective: str = "and", ) -> str: """ Join the given list of strings in a natural manner, with 'and' to join the last two elements. :param iterable: :param use_repr: Whether to join the ``repr`` of each object. :param oxford: Whether to use an oxford comma when joining the last two elements. :default oxford: :py:obj:`False`. Always :py:obj:`False` if there are fewer than three elements :param delimiter: A single character to use between the words. :param connective: The connective to join the final two words with. :rtype: .. versionchanged:: 0.11.0 Added ``delimiter`` and ``connective`` arguments. """ delimiter = f"{delimiter} " if use_repr: words = [repr(w) for w in iterable] else: words = list(iterable) if len(words) == 0: return '' elif len(words) == 1: return words[0] elif len(words) == 2: return f" {connective} ".join(words) else: if oxford: return delimiter.join(words[:-1]) + f"{delimiter}{connective} {words[-1]}" else: return delimiter.join(words[:-1]) + f" {connective} {words[-1]}"
TAB = '\t' """ A literal ``TAB`` (``\\t``) character for use in f-strings. .. versionadded:: 1.3.0 """ CR = '\r' """ The carriage return character (``\\r``) for use in f-strings. .. versionadded:: 1.3.0 """ LF = '\n' """ The newline character (``\\n``) for use in f-strings. .. versionadded:: 1.3.0 """ _docs = domdf_python_tools.__docs
[docs]@prettify_docstrings class Plural(functools.partial): """ Represents a word as its singular and plural. .. versionadded:: 2.0.0 :param singular: The singular form of the word. :param plural: The plural form of the word. .. code-block:: python >>> cow = Plural("cow", "cows") >>> n = 1 >>> print(f"The farmer has {n} {cow(n)}.") The farmer has 1 cow. >>> n = 2 >>> print(f"The farmer has {n} {cow(n)}.") The farmer has 2 cows. >>> n = 3 >>> print(f"The farmer has {n} {cow(n)}.") The farmer has 3 cows. """ if _docs: # pragma: no cover def __init__(self, singular: str, plural: str): pass
[docs] def __call__(self, n: int) -> str: # type: ignore """ Returns either the singular or plural form of the word depending on the value of ``n``. :param n: """
# if PYPY: # pragma: no cover (!PyPy) if PYPY and sys.version_info < (3, 9): # pragma: no cover (!PyPy) def __init__(self, singular: str, plural: str): super().__init__(ngettext, singular, plural) # type: ignore[call-arg] else: # pragma: no cover (!CPython) def __new__(cls, singular: str, plural: str): # noqa: D102 return functools.partial.__new__(cls, ngettext, singular, plural)
[docs] @recursive_repr() def __repr__(self): qualname = type(self).__qualname__ args: List[str] = [] args.extend(repr(x) for x in self.args) args.extend(f"{k}={v!r}" for (k, v) in self.keywords.items()) return f"{qualname}({', '.join(args)})"
[docs]@prettify_docstrings class PluralPhrase(NamedTuple): """ Represents a phrase which varies depending on a numerical count. .. versionadded:: 3.3.0 :param template: The phrase template. :param words: The words to insert into the template. For example, consider the phase:: The proposed changes are to ... The "phrase template" would be: .. code-block:: python "The proposed {} {} to ..." and the two words to insert are: .. code-block:: python Plural("change", "changes") Plural("is", "are") The phrase is constructed as follows: .. code-block:: python >>> phrase = PluralPhrase( ... "The proposed {} {} to ...", ... (Plural("change", "changes"), Plural("is", "are")) ... ) >>> phrase(1) 'The proposed change is to ...' >>> phrase(2) 'The proposed changes are to ...' The phrase template can use any `valid syntax`_ for :meth:`str.format`, except for keyword arguments. The exception if the keyword ``n``, which is replaced with the count (e.g. ``2``) passed in when the phrase is constructed. For example: .. code-block:: python >>> phrase2 = PluralPhrase("The farmer has {n} {0}.", (Plural("cow", "cows"), )) >>> phrase2(2) 'The farmer has 2 cows.' .. _valid syntax: https://docs.python.org/3/library/string.html#formatstrings """ template: str words: Tuple[Plural, ...]
[docs] def __call__(self, n: int) -> str: # noqa: TYP004 # TODO """ Construct the phrase based on the value of ``n``. :param n: """ plural_words = [x(n) for x in self.words] return self.template.format(*plural_words, n=n)
@functools.lru_cache() def _slice_end(max_length: int, ending: str = "...") -> slice: slice_end = max_length - len(ending) return slice(slice_end)
[docs]def truncate_string(string: str, max_length: int, ending: str = "...") -> str: """ Truncate a string to ``max_length`` characters, and put ``ending`` on the end. The truncated string is further truncated by the length of ``ending`` so the returned string is no more then ``max_length``. .. versionadded:: 3.3.0 :param string: :param max_length: :param ending: """ string_length = len(string) if string_length > max_length: return string[_slice_end(max_length, ending)] + ending else: return string