# Copyright 2018 PayTrace, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from io import StringIO
import packaging.version
import yaml
YAML_EXT = '.yml'
PYYAML_REQUIRES_LOADER = packaging.version.parse('5.1') <= packaging.version.parse(yaml.__version__)
[docs]def content_events(value):
"""Return an iterable of events presenting *value* within a YAML document"""
return (
e for e in yaml.parse(StringIO(yaml.dump(value)))
if not isinstance(e, (
yaml.StreamStartEvent,
yaml.DocumentStartEvent,
yaml.DocumentEndEvent,
yaml.StreamEndEvent,
))
)
[docs]class EventsToNodes(yaml.composer.Composer, yaml.resolver.Resolver):
def __init__(self, events):
super(EventsToNodes, self).__init__()
if isinstance(events, list):
events = iter(events)
self.events = events
self.current_event = None
[docs] def check_event(self, *choices):
if self.current_event is None:
self.current_event = next(self.events)
if not choices:
return True
for choice in choices:
if isinstance(self.current_event, choice):
return True
return False
[docs] def peek_event(self):
if self.current_event is None:
self.current_event = next(self.events)
return self.current_event
[docs] def get_event(self):
if self.current_event is None:
self.current_event = next(self.events)
value = self.current_event
self.current_event = None
return value
[docs] def dispose(self):
pass
[docs]def value_from_event_stream(content_events, *, safe_loading=True):
"""Convert an iterable of YAML events to a Pythonic value
The *content_events* MUST NOT include stream or document events.
"""
content_events = iter(content_events)
events = [yaml.StreamStartEvent(), yaml.DocumentStartEvent()]
depth = 0
while True:
events.append(next(content_events))
if isinstance(events[-1], yaml.CollectionStartEvent):
depth += 1
elif isinstance(events[-1], yaml.CollectionEndEvent):
depth -= 1
if depth == 0:
break
events.extend([yaml.DocumentEndEvent(), yaml.StreamEndEvent()])
node = yaml.compose(events, Loader=EventsToNodes)
node_constructor = (
yaml.constructor.SafeConstructor
if safe_loading else
yaml.constructor.Constructor
)()
return node_constructor.construct_object(node, True)
[docs]def get_load_fn(*, safe=True):
if safe:
return yaml.safe_load
if PYYAML_REQUIRES_LOADER:
return yaml.unsafe_load
return yaml.load
[docs]def get_load_all_fn(*, safe=True):
if safe:
return yaml.safe_load_all
if PYYAML_REQUIRES_LOADER:
return yaml.unsafe_load_all
return yaml.load_all