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
13
14from plain.utils.functional import keep_lazy
15
16
17class SafeData:
18    __slots__ = ()
19
20    def __html__(self) -> SafeData:
21        """
22        Return the html representation of a string for interoperability.
23
24        This allows other template engines to understand Plain's SafeData.
25        """
26        return self
27
28
29class SafeString(str, SafeData):
30    """
31    A str subclass that has been specifically marked as "safe" for HTML output
32    purposes.
33    """
34
35    __slots__ = ()
36
37    def __add__(self, rhs: str) -> SafeString | str:  # type: ignore[override]
38        """
39        Concatenating a safe string with another safe bytestring or
40        safe string is safe. Otherwise, the result is no longer safe.
41        """
42        t = super().__add__(rhs)
43        if isinstance(rhs, SafeData):
44            return SafeString(t)
45        return t
46
47    def __str__(self) -> str:
48        return self
49
50
51def _safety_decorator[T](
52    safety_marker: Callable[[Any], T], func: Callable[..., Any]
53) -> Callable[..., T]:
54    @wraps(func)
55    def wrapper(*args: Any, **kwargs: Any) -> T:
56        return safety_marker(func(*args, **kwargs))
57
58    return wrapper
59
60
61@keep_lazy(SafeString)
62def mark_safe(s: Any) -> SafeString | SafeData | Callable[..., Any]:
63    """
64    Explicitly mark a string as safe for (HTML) output purposes. The returned
65    object can be used everywhere a string is appropriate.
66
67    If used on a method as a decorator, mark the returned data as safe.
68
69    Can be called multiple times on a single string.
70    """
71    if hasattr(s, "__html__"):
72        return s
73    if callable(s):
74        return _safety_decorator(mark_safe, s)
75    return SafeString(s)