summaryrefslogtreecommitdiffstats
path: root/tag.py
blob: 9a4cad95b9b7db41fa3e442000537d352dfe3646 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
from __future__ import annotations

"""
In round-trip mode the original tag needs to be preserved, but the tag
transformed based on the directives needs to be available as well.

A Tag that is created during loading has a handle and a suffix.
Not all objects loaded currently have a Tag, that .tag attribute can be None
A Tag that is created for dumping only (on an object loaded without a tag) has a suffix
only.
"""

if False:  # MYPY
    from typing import Any, Dict, Optional, List, Union, Optional, Iterator  # NOQA

tag_attrib = '_yaml_tag'


class Tag:
    """store original tag information for roundtripping"""

    attrib = tag_attrib

    def __init__(self, handle: Any = None, suffix: Any = None, handles: Any = None) -> None:
        self.handle = handle
        self.suffix = suffix
        self.handles = handles
        self._transform_type: Optional[bool] = None

    def __repr__(self) -> str:
        return f'{self.__class__.__name__}({self.trval!r})'

    def __str__(self) -> str:
        return f'{self.trval}'

    def __hash__(self) -> int:
        try:
            return self._hash_id  # type: ignore
        except AttributeError:
            self._hash_id = res = hash((self.handle, self.suffix))
            return res

    def __eq__(self, other: Any) -> bool:
        # other should not be a string, but the serializer sometimes provides these
        if isinstance(other, str):
            return self.trval == other
        return bool(self.trval == other.trval)

    def startswith(self, x: str) -> bool:
        if self.trval is not None:
            return self.trval.startswith(x)
        return False

    @property
    def trval(self) -> Optional[str]:
        try:
            return self._trval
        except AttributeError:
            pass
        if self.handle is None:
            self._trval: Optional[str] = self.uri_decoded_suffix
            return self._trval
        assert self._transform_type is not None
        if not self._transform_type:
            # the non-round-trip case
            self._trval = self.handles[self.handle] + self.uri_decoded_suffix
            return self._trval
        # round-trip case
        if self.handle == '!!' and self.suffix in (
            'null',
            'bool',
            'int',
            'float',
            'binary',
            'timestamp',
            'omap',
            'pairs',
            'set',
            'str',
            'seq',
            'map',
        ):
            self._trval = self.handles[self.handle] + self.uri_decoded_suffix
        else:
            # self._trval = self.handle + self.suffix
            self._trval = self.handles[self.handle] + self.uri_decoded_suffix
        return self._trval

    value = trval

    @property
    def uri_decoded_suffix(self) -> Optional[str]:
        try:
            return self._uri_decoded_suffix
        except AttributeError:
            pass
        if self.suffix is None:
            self._uri_decoded_suffix: Optional[str] = None
            return None
        res = ''
        # don't have to check for scanner errors here
        idx = 0
        while idx < len(self.suffix):
            ch = self.suffix[idx]
            idx += 1
            if ch != '%':
                res += ch
            else:
                res += chr(int(self.suffix[idx : idx + 2], 16))
                idx += 2
        self._uri_decoded_suffix = res
        return res

    def select_transform(self, val: bool) -> None:
        """
        val: False -> non-round-trip
             True -> round-trip
        """
        assert self._transform_type is None
        self._transform_type = val

    def check_handle(self) -> bool:
        if self.handle is None:
            return False
        return self.handle not in self.handles