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

169 statements  

« prev     ^ index     » next       coverage.py v7.4.0, created at 2024-01-22 06:29 +0000

1import functools 

2import string 

3import sys 

4import typing as t 

5 

6if t.TYPE_CHECKING: 

7 import typing_extensions as te 

8 

9 class HasHTML(te.Protocol): 

10 def __html__(self) -> str: 

11 pass 

12 

13 _P = te.ParamSpec("_P") 

14 

15 

16__version__ = "2.1.4" 

17 

18 

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

20 @functools.wraps(func) 

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

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

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

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

25 

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

27 

28 

29class Markup(str): 

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

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

32 safe. 

33 

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

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

36 :meth:`escape` class method instead. 

37 

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

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

40 >>> Markup(42) 

41 Markup('42') 

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

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

44 

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

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

47 output of that method, marking it safe. 

48 

49 >>> class Foo: 

50 ... def __html__(self): 

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

52 ... 

53 >>> Markup(Foo()) 

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

55 

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

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

58 

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

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

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

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

63 """ 

64 

65 __slots__ = () 

66 

67 def __new__( 

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

69 ) -> "te.Self": 

70 if hasattr(base, "__html__"): 

71 base = base.__html__() 

72 

73 if encoding is None: 

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

75 

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

77 

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

79 return self 

80 

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

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

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

84 

85 return NotImplemented 

86 

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

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

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

90 

91 return NotImplemented 

92 

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

94 if isinstance(num, int): 

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

96 

97 return NotImplemented 

98 

99 __rmul__ = __mul__ 

100 

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

102 if isinstance(arg, tuple): 

103 # a tuple of arguments, each wrapped 

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

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

106 # a mapping of arguments, wrapped 

107 arg = _MarkupEscapeHelper(arg, self.escape) 

108 else: 

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

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

111 

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

113 

114 def __repr__(self) -> str: 

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

116 

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

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

119 

120 join.__doc__ = str.join.__doc__ 

121 

122 def split( # type: ignore[override] 

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

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

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

126 

127 split.__doc__ = str.split.__doc__ 

128 

129 def rsplit( # type: ignore[override] 

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

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

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

133 

134 rsplit.__doc__ = str.rsplit.__doc__ 

135 

136 def splitlines( # type: ignore[override] 

137 self, keepends: bool = False 

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

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

140 

141 splitlines.__doc__ = str.splitlines.__doc__ 

142 

143 def unescape(self) -> str: 

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

145 HTML entities with the characters they represent. 

146 

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

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

149 """ 

150 from html import unescape 

151 

152 return unescape(str(self)) 

153 

154 def striptags(self) -> str: 

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

156 whitespace to single spaces. 

157 

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

159 'Main » About' 

160 """ 

161 # collapse spaces 

162 value = " ".join(self.split()) 

163 

164 # Look for comments then tags separately. Otherwise, a comment that 

165 # contains a tag would end early, leaving some of the comment behind. 

166 

167 while True: 

168 # keep finding comment start marks 

169 start = value.find("<!--") 

170 

171 if start == -1: 

172 break 

173 

174 # find a comment end mark beyond the start, otherwise stop 

175 end = value.find("-->", start) 

176 

177 if end == -1: 

178 break 

179 

180 value = f"{value[:start]}{value[end + 3:]}" 

181 

182 # remove tags using the same method 

183 while True: 

184 start = value.find("<") 

185 

186 if start == -1: 

187 break 

188 

189 end = value.find(">", start) 

190 

191 if end == -1: 

192 break 

193 

194 value = f"{value[:start]}{value[end + 1:]}" 

195 

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

197 

198 @classmethod 

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

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

201 subclasses the correct type is returned. 

202 """ 

203 rv = escape(s) 

204 

205 if rv.__class__ is not cls: 

206 return cls(rv) 

207 

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

209 

210 __getitem__ = _simple_escaping_wrapper(str.__getitem__) 

211 capitalize = _simple_escaping_wrapper(str.capitalize) 

212 title = _simple_escaping_wrapper(str.title) 

213 lower = _simple_escaping_wrapper(str.lower) 

214 upper = _simple_escaping_wrapper(str.upper) 

215 replace = _simple_escaping_wrapper(str.replace) 

216 ljust = _simple_escaping_wrapper(str.ljust) 

217 rjust = _simple_escaping_wrapper(str.rjust) 

218 lstrip = _simple_escaping_wrapper(str.lstrip) 

219 rstrip = _simple_escaping_wrapper(str.rstrip) 

220 center = _simple_escaping_wrapper(str.center) 

221 strip = _simple_escaping_wrapper(str.strip) 

222 translate = _simple_escaping_wrapper(str.translate) 

223 expandtabs = _simple_escaping_wrapper(str.expandtabs) 

224 swapcase = _simple_escaping_wrapper(str.swapcase) 

225 zfill = _simple_escaping_wrapper(str.zfill) 

226 casefold = _simple_escaping_wrapper(str.casefold) 

227 

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

229 removeprefix = _simple_escaping_wrapper(str.removeprefix) 

230 removesuffix = _simple_escaping_wrapper(str.removesuffix) 

231 

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

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

234 cls = self.__class__ 

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

236 

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

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

239 cls = self.__class__ 

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

241 

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

243 formatter = EscapeFormatter(self.escape) 

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

245 

246 def format_map( # type: ignore[override] 

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

248 ) -> "te.Self": 

249 formatter = EscapeFormatter(self.escape) 

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

251 

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

253 if format_spec: 

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

255 

256 return self 

257 

258 

259class EscapeFormatter(string.Formatter): 

260 __slots__ = ("escape",) 

261 

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

263 self.escape = escape 

264 super().__init__() 

265 

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

267 if hasattr(value, "__html_format__"): 

268 rv = value.__html_format__(format_spec) 

269 elif hasattr(value, "__html__"): 

270 if format_spec: 

271 raise ValueError( 

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

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

274 " __html_format__ to work with format specifiers." 

275 ) 

276 rv = value.__html__() 

277 else: 

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

279 # otherwise the wrong callback methods are invoked. 

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

281 return str(self.escape(rv)) 

282 

283 

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

285 

286 

287def _escape_argspec( 

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

289) -> _ListOrDict: 

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

291 for key, value in iterable: 

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

293 obj[key] = escape(value) 

294 

295 return obj 

296 

297 

298class _MarkupEscapeHelper: 

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

300 

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

302 

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

304 self.obj = obj 

305 self.escape = escape 

306 

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

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

309 

310 def __str__(self) -> str: 

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

312 

313 def __repr__(self) -> str: 

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

315 

316 def __int__(self) -> int: 

317 return int(self.obj) 

318 

319 def __float__(self) -> float: 

320 return float(self.obj) 

321 

322 

323# circular import 

324try: 

325 from ._speedups import escape as escape 

326 from ._speedups import escape_silent as escape_silent 

327 from ._speedups import soft_str as soft_str 

328except ImportError: 

329 from ._native import escape as escape 

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

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