Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/mistune/util.py: 100%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

47 statements  

1import re 

2import html 

3from typing import Callable, Match, cast 

4from urllib.parse import quote 

5 

6_expand_tab_re = re.compile(r"^( {0,3})\t", flags=re.M) 

7_replace_charref = cast(Callable[[Match[str]], str], getattr(html, "_replace_charref")) 

8 

9 

10def expand_leading_tab(text: str, width: int = 4) -> str: 

11 def repl(m: Match[str]) -> str: 

12 s = m.group(1) 

13 return s + " " * (width - len(s)) 

14 

15 return _expand_tab_re.sub(repl, text) 

16 

17 

18def expand_tab(text: str, space: str = " ") -> str: 

19 repl = r"\1" + space 

20 return _expand_tab_re.sub(repl, text) 

21 

22 

23def escape(s: str, quote: bool = True) -> str: 

24 """Escape characters of ``&<>``. If quote=True, ``"`` will be 

25 converted to ``&quote;``.""" 

26 s = s.replace("&", "&amp;") 

27 s = s.replace("<", "&lt;") 

28 s = s.replace(">", "&gt;") 

29 if quote: 

30 s = s.replace('"', "&quot;") 

31 return s 

32 

33 

34def escape_url(link: str) -> str: 

35 """Escape URL for safety.""" 

36 safe = ( 

37 ":/?#@" # gen-delims - '[]' (rfc3986) 

38 "!$&()*+,;=" # sub-delims - "'" (rfc3986) 

39 "%" # leave already-encoded octets alone 

40 ) 

41 return quote(unescape(link), safe=safe) 

42 

43 

44def safe_entity(s: str) -> str: 

45 """Escape characters for safety.""" 

46 return escape(unescape(s)) 

47 

48 

49def unikey(s: str) -> str: 

50 """Generate a unique key for links and footnotes.""" 

51 key = " ".join(s.split()).strip() 

52 return key.lower().upper() 

53 

54 

55_charref_re = re.compile( 

56 r"&(#[0-9]{1,7};" 

57 r"|#[xX][0-9a-fA-F]+;" 

58 r"|[^\t\n\f <&#;]{1,32};)" 

59) 

60 

61 

62def unescape(s: str) -> str: 

63 """ 

64 Copy from `html.unescape`, but `_charref` is different. CommonMark 

65 does not accept entity references without a trailing semicolon 

66 """ 

67 if "&" not in s: 

68 return s 

69 return _charref_re.sub(_replace_charref, s) 

70 

71 

72_striptags_re = re.compile(r"(<!--.*?-->|<[^>]*>)") 

73_strip_image_re = re.compile(r"<img\b[^>]*\balt=(\"([^\"]*)\"|'([^']*)')[^>]*>") 

74 

75 

76def striptags(s: str) -> str: 

77 s = _strip_image_re.sub(lambda m: m.group(2) or m.group(3) or "", s) 

78 return _striptags_re.sub("", s) 

79 

80 

81def strip_end(src: str) -> str: 

82 r"""Strip trailing whitespace after the final line break. 

83 

84 This used to be implemented as ``re.sub(r"\n\s+$", "\n", src)``. 

85 For a long run of blank lines followed by a non-whitespace continuation, 

86 the regex retries ``\s+`` from every preceding newline and becomes 

87 quadratic. Scanning the suffix once keeps the same behavior in linear 

88 time. 

89 """ 

90 end = len(src) 

91 while end and src[end - 1].isspace(): 

92 end -= 1 

93 

94 newline = src.find("\n", end) 

95 if newline >= 0: 

96 return src[:newline] + "\n" 

97 return src