1"""
2Functions for working with "safe strings": strings that can be displayed safely
3without further escaping in HTML. Marking something as a "safe string" means
4that the producer of the string has already turned characters that should not
5be interpreted by the HTML engine (e.g. '<') into the appropriate entities.
6"""
7
8from __future__ import annotations
9
10from collections.abc import Callable
11from functools import wraps
12from typing import Any, TypeVar
13
14from plain.utils.functional import keep_lazy
15
16_T = TypeVar("_T")
17
18
19class SafeData:
20 __slots__ = ()
21
22 def __html__(self) -> SafeData:
23 """
24 Return the html representation of a string for interoperability.
25
26 This allows other template engines to understand Plain's SafeData.
27 """
28 return self
29
30
31class SafeString(str, SafeData):
32 """
33 A str subclass that has been specifically marked as "safe" for HTML output
34 purposes.
35 """
36
37 __slots__ = ()
38
39 def __add__(self, rhs: str) -> SafeString | str:
40 """
41 Concatenating a safe string with another safe bytestring or
42 safe string is safe. Otherwise, the result is no longer safe.
43 """
44 t = super().__add__(rhs)
45 if isinstance(rhs, SafeData):
46 return SafeString(t)
47 return t
48
49 def __str__(self) -> str:
50 return self
51
52
53def _safety_decorator(
54 safety_marker: Callable[[Any], _T], func: Callable[..., Any]
55) -> Callable[..., _T]:
56 @wraps(func)
57 def wrapper(*args: Any, **kwargs: Any) -> _T:
58 return safety_marker(func(*args, **kwargs))
59
60 return wrapper
61
62
63@keep_lazy(SafeString)
64def mark_safe(s: Any) -> SafeString | SafeData | Callable[..., Any]:
65 """
66 Explicitly mark a string as safe for (HTML) output purposes. The returned
67 object can be used everywhere a string is appropriate.
68
69 If used on a method as a decorator, mark the returned data as safe.
70
71 Can be called multiple times on a single string.
72 """
73 if hasattr(s, "__html__"):
74 return s
75 if callable(s):
76 return _safety_decorator(mark_safe, s)
77 return SafeString(s)