Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/markupsafe/__init__.py: 73%

160 statements  

« prev     ^ index     » next       coverage.py v7.3.1, created at 2023-09-25 06:15 +0000

1import functools 

2import re 

3import string 

4import sys 

5import typing as t 

6 

7if t.TYPE_CHECKING: 

8 import typing_extensions as te 

9 

10 class HasHTML(te.Protocol): 

11 def __html__(self) -> str: 

12 pass 

13 

14 _P = te.ParamSpec("_P") 

15 

16 

17__version__ = "2.1.3" 

18 

19_strip_comments_re = re.compile(r"<!--.*?-->", re.DOTALL) 

20_strip_tags_re = re.compile(r"<.*?>", re.DOTALL) 

21 

22 

23def _simple_escaping_wrapper(func: "t.Callable[_P, str]") -> "t.Callable[_P, Markup]": 

24 @functools.wraps(func) 

25 def wrapped(self: "Markup", *args: "_P.args", **kwargs: "_P.kwargs") -> "Markup": 

26 arg_list = _escape_argspec(list(args), enumerate(args), self.escape) 

27 _escape_argspec(kwargs, kwargs.items(), self.escape) 

28 return self.__class__(func(self, *arg_list, **kwargs)) # type: ignore[arg-type] 

29 

30 return wrapped # type: ignore[return-value] 

31 

32 

33class Markup(str): 

34 """A string that is ready to be safely inserted into an HTML or XML 

35 document, either because it was escaped or because it was marked 

36 safe. 

37 

38 Passing an object to the constructor converts it to text and wraps 

39 it to mark it safe without escaping. To escape the text, use the 

40 :meth:`escape` class method instead. 

41 

42 >>> Markup("Hello, <em>World</em>!") 

43 Markup('Hello, <em>World</em>!') 

44 >>> Markup(42) 

45 Markup('42') 

46 >>> Markup.escape("Hello, <em>World</em>!") 

47 Markup('Hello &lt;em&gt;World&lt;/em&gt;!') 

48 

49 This implements the ``__html__()`` interface that some frameworks 

50 use. Passing an object that implements ``__html__()`` will wrap the 

51 output of that method, marking it safe. 

52 

53 >>> class Foo: 

54 ... def __html__(self): 

55 ... return '<a href="/foo">foo</a>' 

56 ... 

57 >>> Markup(Foo()) 

58 Markup('<a href="/foo">foo</a>') 

59 

60 This is a subclass of :class:`str`. It has the same methods, but 

61 escapes their arguments and returns a ``Markup`` instance. 

62 

63 >>> Markup("<em>%s</em>") % ("foo & bar",) 

64 Markup('<em>foo &amp; bar</em>') 

65 >>> Markup("<em>Hello</em> ") + "<foo>" 

66 Markup('<em>Hello</em> &lt;foo&gt;') 

67 """ 

68 

69 __slots__ = () 

70 

71 def __new__( 

72 cls, base: t.Any = "", encoding: t.Optional[str] = None, errors: str = "strict" 

73 ) -> "te.Self": 

74 if hasattr(base, "__html__"): 

75 base = base.__html__() 

76 

77 if encoding is None: 

78 return super().__new__(cls, base) 

79 

80 return super().__new__(cls, base, encoding, errors) 

81 

82 def __html__(self) -> "te.Self": 

83 return self 

84 

85 def __add__(self, other: t.Union[str, "HasHTML"]) -> "te.Self": 

86 if isinstance(other, str) or hasattr(other, "__html__"): 

87 return self.__class__(super().__add__(self.escape(other))) 

88 

89 return NotImplemented 

90 

91 def __radd__(self, other: t.Union[str, "HasHTML"]) -> "te.Self": 

92 if isinstance(other, str) or hasattr(other, "__html__"): 

93 return self.escape(other).__add__(self) 

94 

95 return NotImplemented 

96 

97 def __mul__(self, num: "te.SupportsIndex") -> "te.Self": 

98 if isinstance(num, int): 

99 return self.__class__(super().__mul__(num)) 

100 

101 return NotImplemented 

102 

103 __rmul__ = __mul__ 

104 

105 def __mod__(self, arg: t.Any) -> "te.Self": 

106 if isinstance(arg, tuple): 

107 # a tuple of arguments, each wrapped 

108 arg = tuple(_MarkupEscapeHelper(x, self.escape) for x in arg) 

109 elif hasattr(type(arg), "__getitem__") and not isinstance(arg, str): 

110 # a mapping of arguments, wrapped 

111 arg = _MarkupEscapeHelper(arg, self.escape) 

112 else: 

113 # a single argument, wrapped with the helper and a tuple 

114 arg = (_MarkupEscapeHelper(arg, self.escape),) 

115 

116 return self.__class__(super().__mod__(arg)) 

117 

118 def __repr__(self) -> str: 

119 return f"{self.__class__.__name__}({super().__repr__()})" 

120 

121 def join(self, seq: t.Iterable[t.Union[str, "HasHTML"]]) -> "te.Self": 

122 return self.__class__(super().join(map(self.escape, seq))) 

123 

124 join.__doc__ = str.join.__doc__ 

125 

126 def split( # type: ignore[override] 

127 self, sep: t.Optional[str] = None, maxsplit: int = -1 

128 ) -> t.List["te.Self"]: 

129 return [self.__class__(v) for v in super().split(sep, maxsplit)] 

130 

131 split.__doc__ = str.split.__doc__ 

132 

133 def rsplit( # type: ignore[override] 

134 self, sep: t.Optional[str] = None, maxsplit: int = -1 

135 ) -> t.List["te.Self"]: 

136 return [self.__class__(v) for v in super().rsplit(sep, maxsplit)] 

137 

138 rsplit.__doc__ = str.rsplit.__doc__ 

139 

140 def splitlines( # type: ignore[override] 

141 self, keepends: bool = False 

142 ) -> t.List["te.Self"]: 

143 return [self.__class__(v) for v in super().splitlines(keepends)] 

144 

145 splitlines.__doc__ = str.splitlines.__doc__ 

146 

147 def unescape(self) -> str: 

148 """Convert escaped markup back into a text string. This replaces 

149 HTML entities with the characters they represent. 

150 

151 >>> Markup("Main &raquo; <em>About</em>").unescape() 

152 'Main » <em>About</em>' 

153 """ 

154 from html import unescape 

155 

156 return unescape(str(self)) 

157 

158 def striptags(self) -> str: 

159 """:meth:`unescape` the markup, remove tags, and normalize 

160 whitespace to single spaces. 

161 

162 >>> Markup("Main &raquo;\t<em>About</em>").striptags() 

163 'Main » About' 

164 """ 

165 # Use two regexes to avoid ambiguous matches. 

166 value = _strip_comments_re.sub("", self) 

167 value = _strip_tags_re.sub("", value) 

168 value = " ".join(value.split()) 

169 return self.__class__(value).unescape() 

170 

171 @classmethod 

172 def escape(cls, s: t.Any) -> "te.Self": 

173 """Escape a string. Calls :func:`escape` and ensures that for 

174 subclasses the correct type is returned. 

175 """ 

176 rv = escape(s) 

177 

178 if rv.__class__ is not cls: 

179 return cls(rv) 

180 

181 return rv # type: ignore[return-value] 

182 

183 __getitem__ = _simple_escaping_wrapper(str.__getitem__) 

184 capitalize = _simple_escaping_wrapper(str.capitalize) 

185 title = _simple_escaping_wrapper(str.title) 

186 lower = _simple_escaping_wrapper(str.lower) 

187 upper = _simple_escaping_wrapper(str.upper) 

188 replace = _simple_escaping_wrapper(str.replace) 

189 ljust = _simple_escaping_wrapper(str.ljust) 

190 rjust = _simple_escaping_wrapper(str.rjust) 

191 lstrip = _simple_escaping_wrapper(str.lstrip) 

192 rstrip = _simple_escaping_wrapper(str.rstrip) 

193 center = _simple_escaping_wrapper(str.center) 

194 strip = _simple_escaping_wrapper(str.strip) 

195 translate = _simple_escaping_wrapper(str.translate) 

196 expandtabs = _simple_escaping_wrapper(str.expandtabs) 

197 swapcase = _simple_escaping_wrapper(str.swapcase) 

198 zfill = _simple_escaping_wrapper(str.zfill) 

199 casefold = _simple_escaping_wrapper(str.casefold) 

200 

201 if sys.version_info >= (3, 9): 

202 removeprefix = _simple_escaping_wrapper(str.removeprefix) 

203 removesuffix = _simple_escaping_wrapper(str.removesuffix) 

204 

205 def partition(self, sep: str) -> t.Tuple["te.Self", "te.Self", "te.Self"]: 

206 l, s, r = super().partition(self.escape(sep)) 

207 cls = self.__class__ 

208 return cls(l), cls(s), cls(r) 

209 

210 def rpartition(self, sep: str) -> t.Tuple["te.Self", "te.Self", "te.Self"]: 

211 l, s, r = super().rpartition(self.escape(sep)) 

212 cls = self.__class__ 

213 return cls(l), cls(s), cls(r) 

214 

215 def format(self, *args: t.Any, **kwargs: t.Any) -> "te.Self": 

216 formatter = EscapeFormatter(self.escape) 

217 return self.__class__(formatter.vformat(self, args, kwargs)) 

218 

219 def format_map( # type: ignore[override] 

220 self, map: t.Mapping[str, t.Any] 

221 ) -> "te.Self": 

222 formatter = EscapeFormatter(self.escape) 

223 return self.__class__(formatter.vformat(self, (), map)) 

224 

225 def __html_format__(self, format_spec: str) -> "te.Self": 

226 if format_spec: 

227 raise ValueError("Unsupported format specification for Markup.") 

228 

229 return self 

230 

231 

232class EscapeFormatter(string.Formatter): 

233 __slots__ = ("escape",) 

234 

235 def __init__(self, escape: t.Callable[[t.Any], Markup]) -> None: 

236 self.escape = escape 

237 super().__init__() 

238 

239 def format_field(self, value: t.Any, format_spec: str) -> str: 

240 if hasattr(value, "__html_format__"): 

241 rv = value.__html_format__(format_spec) 

242 elif hasattr(value, "__html__"): 

243 if format_spec: 

244 raise ValueError( 

245 f"Format specifier {format_spec} given, but {type(value)} does not" 

246 " define __html_format__. A class that defines __html__ must define" 

247 " __html_format__ to work with format specifiers." 

248 ) 

249 rv = value.__html__() 

250 else: 

251 # We need to make sure the format spec is str here as 

252 # otherwise the wrong callback methods are invoked. 

253 rv = string.Formatter.format_field(self, value, str(format_spec)) 

254 return str(self.escape(rv)) 

255 

256 

257_ListOrDict = t.TypeVar("_ListOrDict", list, dict) 

258 

259 

260def _escape_argspec( 

261 obj: _ListOrDict, iterable: t.Iterable[t.Any], escape: t.Callable[[t.Any], Markup] 

262) -> _ListOrDict: 

263 """Helper for various string-wrapped functions.""" 

264 for key, value in iterable: 

265 if isinstance(value, str) or hasattr(value, "__html__"): 

266 obj[key] = escape(value) 

267 

268 return obj 

269 

270 

271class _MarkupEscapeHelper: 

272 """Helper for :meth:`Markup.__mod__`.""" 

273 

274 __slots__ = ("obj", "escape") 

275 

276 def __init__(self, obj: t.Any, escape: t.Callable[[t.Any], Markup]) -> None: 

277 self.obj = obj 

278 self.escape = escape 

279 

280 def __getitem__(self, item: t.Any) -> "te.Self": 

281 return self.__class__(self.obj[item], self.escape) 

282 

283 def __str__(self) -> str: 

284 return str(self.escape(self.obj)) 

285 

286 def __repr__(self) -> str: 

287 return str(self.escape(repr(self.obj))) 

288 

289 def __int__(self) -> int: 

290 return int(self.obj) 

291 

292 def __float__(self) -> float: 

293 return float(self.obj) 

294 

295 

296# circular import 

297try: 

298 from ._speedups import escape as escape 

299 from ._speedups import escape_silent as escape_silent 

300 from ._speedups import soft_str as soft_str 

301except ImportError: 

302 from ._native import escape as escape 

303 from ._native import escape_silent as escape_silent # noqa: F401 

304 from ._native import soft_str as soft_str # noqa: F401