about summary refs log tree commit diff
path: root/.venv/lib/python3.12/site-packages/strictyaml/parser.py
blob: b19245be0fb26dc42df0b2f1ff499a9be6aae010 (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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
"""
Parsing code for strictyaml.
"""
import sys

from strictyaml import ruamel as ruamelyaml
from strictyaml import exceptions
from strictyaml.ruamel.comments import CommentedSeq, CommentedMap

from strictyaml.any_validator import Any
from strictyaml.yamllocation import YAMLChunk
from strictyaml import utils

from strictyaml.ruamel.reader import Reader
from strictyaml.ruamel.scanner import RoundTripScanner
from strictyaml.ruamel.parser import RoundTripParser
from strictyaml.ruamel.composer import Composer
from strictyaml.ruamel.constructor import RoundTripConstructor
from strictyaml.ruamel.resolver import VersionedResolver
from strictyaml.ruamel.nodes import MappingNode
from strictyaml.ruamel.compat import PY2
from strictyaml.ruamel.constructor import ConstructorError

if sys.version_info[:2] > (3, 4):
    from collections.abc import Hashable
else:
    from collections import Hashable


# StrictYAMLConstructor is mostly taken from RoundTripConstructor ruamel/yaml/constructor.py
# Differences:
#  * If a duplicate key is found, an exception is raised


class StrictYAMLConstructor(RoundTripConstructor):
    yaml_constructors = {}

    def construct_mapping(self, node, maptyp, deep=False):
        if not isinstance(node, MappingNode):
            raise ConstructorError(
                None,
                None,
                "expected a mapping node, but found %s" % node.id,
                node.start_mark,
            )
        merge_map = self.flatten_mapping(node)

        # mapping = {}
        if node.comment:
            maptyp._yaml_add_comment(node.comment[:2])
            if len(node.comment) > 2:
                maptyp.yaml_end_comment_extend(node.comment[2], clear=True)
        if node.anchor:
            from strictyaml.ruamel.serializer import templated_id

            if not templated_id(node.anchor):
                maptyp.yaml_set_anchor(node.anchor)
        for key_node, value_node in node.value:
            # keys can be list -> deep
            key = self.construct_object(key_node, deep=True)
            # lists are not hashable, but tuples are
            if not isinstance(key, Hashable):
                if isinstance(key, list):
                    key = tuple(key)
            if PY2:
                try:
                    hash(key)
                except TypeError as exc:
                    raise ConstructorError(
                        "while constructing a mapping",
                        node.start_mark,
                        "found unacceptable key (%s)" % exc,
                        key_node.start_mark,
                    )
            else:
                if not isinstance(key, Hashable):
                    raise ConstructorError(
                        "while constructing a mapping",
                        node.start_mark,
                        "found unhashable key",
                        key_node.start_mark,
                    )
            value = self.construct_object(value_node, deep=deep)
            if key_node.comment:
                maptyp._yaml_add_comment(key_node.comment, key=key)
            if value_node.comment:
                maptyp._yaml_add_comment(value_node.comment, value=key)
            maptyp._yaml_set_kv_line_col(
                key,
                [
                    key_node.start_mark.line,
                    key_node.start_mark.column,
                    value_node.start_mark.line,
                    value_node.start_mark.column,
                ],
            )
            if key in maptyp:
                key_node.start_mark.name = self.label
                key_node.end_mark.name = self.label

                raise exceptions.DuplicateKeysDisallowed(
                    "While parsing",
                    key_node.start_mark,
                    "Duplicate key '{0}' found".format(key),
                    key_node.end_mark,
                )
            maptyp[key] = value
        # do this last, or <<: before a key will prevent insertion in instances
        # of collections.OrderedDict (as they have no __contains__
        if merge_map:
            maptyp.add_yaml_merge(merge_map)

        # Don't verify Mapping indentation when allowing flow,
        # as that disallows:
        #   short_key: { x = 1 }
        #   very_long_key: { x = 1 }
        if not self.allow_flow_style:
            previous_indentation = None

            for node in [
                nodegroup[1]
                for nodegroup in node.value
                if isinstance(nodegroup[1], ruamelyaml.nodes.MappingNode)
            ]:
                if previous_indentation is None:
                    previous_indentation = node.start_mark.column
                if node.start_mark.column != previous_indentation:
                    raise exceptions.InconsistentIndentationDisallowed(
                        "While parsing",
                        node.start_mark,
                        "Found mapping with indentation "
                        "inconsistent with previous mapping",
                        node.end_mark,
                    )


StrictYAMLConstructor.add_constructor(
    "tag:yaml.org,2002:null", RoundTripConstructor.construct_yaml_str
)

StrictYAMLConstructor.add_constructor(
    "tag:yaml.org,2002:bool", RoundTripConstructor.construct_yaml_str
)

StrictYAMLConstructor.add_constructor(
    "tag:yaml.org,2002:int", RoundTripConstructor.construct_yaml_str
)

StrictYAMLConstructor.add_constructor(
    "tag:yaml.org,2002:float", RoundTripConstructor.construct_yaml_str
)

StrictYAMLConstructor.add_constructor(
    "tag:yaml.org,2002:binary", RoundTripConstructor.construct_yaml_str
)

StrictYAMLConstructor.add_constructor(
    "tag:yaml.org,2002:timestamp", RoundTripConstructor.construct_yaml_str
)

StrictYAMLConstructor.add_constructor(
    "tag:yaml.org,2002:omap", RoundTripConstructor.construct_yaml_omap
)

StrictYAMLConstructor.add_constructor(
    "tag:yaml.org,2002:pairs", RoundTripConstructor.construct_yaml_pairs
)

StrictYAMLConstructor.add_constructor(
    "tag:yaml.org,2002:set", RoundTripConstructor.construct_yaml_set
)

StrictYAMLConstructor.add_constructor(
    "tag:yaml.org,2002:str", RoundTripConstructor.construct_yaml_str
)

StrictYAMLConstructor.add_constructor(
    "tag:yaml.org,2002:seq", RoundTripConstructor.construct_yaml_seq
)

StrictYAMLConstructor.add_constructor(
    "tag:yaml.org,2002:map", RoundTripConstructor.construct_yaml_map
)

StrictYAMLConstructor.add_constructor(None, RoundTripConstructor.construct_undefined)


# StrictYAMLScanner is mostly taken from RoundTripScanner in ruamel/yaml/scanner.py
# Differences:
#  * Tokens are checked for disallowed tokens.


class StrictYAMLScanner(RoundTripScanner):
    def check_token(self, *choices):
        # Check if the next token is one of the given types.
        while self.need_more_tokens():
            self.fetch_more_tokens()
        self._gather_comments()
        if self.tokens:
            if not choices:
                return True
            for choice in choices:
                if isinstance(self.tokens[0], choice):
                    token = self.tokens[0]
                    token.start_mark.name = self.label
                    token.end_mark.name = self.label

                    if isinstance(token, ruamelyaml.tokens.TagToken):
                        raise exceptions.TagTokenDisallowed(
                            "While scanning",
                            token.end_mark,
                            "Found disallowed tag tokens "
                            "(do not specify types in markup)",
                            token.start_mark,
                        )
                    if not self.allow_flow_style:
                        if isinstance(
                            token, ruamelyaml.tokens.FlowMappingStartToken
                        ) or isinstance(
                            token, ruamelyaml.tokens.FlowSequenceStartToken
                        ):
                            raise exceptions.FlowMappingDisallowed(
                                "While scanning",
                                token.start_mark,
                                "Found ugly disallowed JSONesque flow mapping "
                                "(surround with ' and ' to make text appear literally)",
                                token.end_mark,
                            )
                    if isinstance(token, ruamelyaml.tokens.AnchorToken):
                        raise exceptions.AnchorTokenDisallowed(
                            "While scanning",
                            token.start_mark,
                            "Found confusing disallowed anchor token "
                            "(surround with ' and ' to make text appear literally)",
                            token.end_mark,
                        )
                    return True
        return False


class StrictYAMLLoader(
    Reader,
    StrictYAMLScanner,
    RoundTripParser,
    Composer,
    StrictYAMLConstructor,
    VersionedResolver,
):
    def __init__(self, stream, version=None, preserve_quotes=None):
        Reader.__init__(self, stream, loader=self)
        StrictYAMLScanner.__init__(self, loader=self)
        RoundTripParser.__init__(self, loader=self)
        Composer.__init__(self, loader=self)
        StrictYAMLConstructor.__init__(
            self, preserve_quotes=preserve_quotes, loader=self
        )
        VersionedResolver.__init__(self, version, loader=self)


def as_document(data, schema=None, label="<unicode string>"):
    """
    Translate dicts/lists and scalar (string/bool/float/int/etc.) values into a
    YAML object which can be dumped out.
    """
    if schema is None:
        schema = Any()

    return schema(YAMLChunk(schema.to_yaml(data), label=label))


def generic_load(
    yaml_string, schema=None, label="<unicode string>", allow_flow_style=False
):
    if not utils.is_string(yaml_string):
        raise TypeError("StrictYAML can only read a string of valid YAML.")

    # We manufacture a class that has the label we want
    DynamicStrictYAMLLoader = type(
        "DynamicStrictYAMLLoader",
        (StrictYAMLLoader,),
        {"label": label, "allow_flow_style": allow_flow_style},
    )

    try:
        document = ruamelyaml.load(yaml_string, Loader=DynamicStrictYAMLLoader)
    except ruamelyaml.YAMLError as parse_error:
        if parse_error.context_mark is not None:
            parse_error.context_mark.name = label
        if parse_error.problem_mark is not None:
            parse_error.problem_mark.name = label

        raise parse_error

    # Document is just a (string, int, etc.)
    if type(document) not in (CommentedMap, CommentedSeq):
        document = yaml_string

    if schema is None:
        schema = Any()

    return schema(YAMLChunk(document, label=label))


def dirty_load(
    yaml_string, schema=None, label="<unicode string>", allow_flow_style=False
):
    """
    Parse the first YAML document in a string
    and produce corresponding YAML object.

    If allow_flow_style is set to True, then flow style is allowed.
    """
    return generic_load(
        yaml_string, schema=schema, label=label, allow_flow_style=allow_flow_style
    )


def load(yaml_string, schema=None, label="<unicode string>"):
    """
    Parse the first YAML document in a string
    and produce corresponding YAML object.
    """
    return generic_load(yaml_string, schema=schema, label=label)