286 lines
7.6 KiB
Python
286 lines
7.6 KiB
Python
|
# vim:fileencoding=utf-8:noet
|
||
|
from __future__ import (unicode_literals, division, absolute_import, print_function)
|
||
|
|
||
|
import collections
|
||
|
import types
|
||
|
|
||
|
from functools import wraps
|
||
|
|
||
|
from powerline.lint.markedjson.error import MarkedError
|
||
|
|
||
|
from powerline.lint.markedjson import nodes
|
||
|
from powerline.lint.markedjson.markedvalue import gen_marked_value
|
||
|
from powerline.lib.unicode import unicode
|
||
|
|
||
|
|
||
|
def marked(func):
|
||
|
@wraps(func)
|
||
|
def f(self, node, *args, **kwargs):
|
||
|
return gen_marked_value(func(self, node, *args, **kwargs), node.start_mark)
|
||
|
return f
|
||
|
|
||
|
|
||
|
class ConstructorError(MarkedError):
|
||
|
pass
|
||
|
|
||
|
|
||
|
class BaseConstructor:
|
||
|
yaml_constructors = {}
|
||
|
|
||
|
def __init__(self):
|
||
|
self.constructed_objects = {}
|
||
|
self.state_generators = []
|
||
|
self.deep_construct = False
|
||
|
|
||
|
def check_data(self):
|
||
|
# If there are more documents available?
|
||
|
return self.check_node()
|
||
|
|
||
|
def get_data(self):
|
||
|
# Construct and return the next document.
|
||
|
if self.check_node():
|
||
|
return self.construct_document(self.get_node())
|
||
|
|
||
|
def get_single_data(self):
|
||
|
# Ensure that the stream contains a single document and construct it.
|
||
|
node = self.get_single_node()
|
||
|
if node is not None:
|
||
|
return self.construct_document(node)
|
||
|
return None
|
||
|
|
||
|
def construct_document(self, node):
|
||
|
data = self.construct_object(node)
|
||
|
while self.state_generators:
|
||
|
state_generators = self.state_generators
|
||
|
self.state_generators = []
|
||
|
for generator in state_generators:
|
||
|
for dummy in generator:
|
||
|
pass
|
||
|
self.constructed_objects = {}
|
||
|
self.deep_construct = False
|
||
|
return data
|
||
|
|
||
|
def construct_object(self, node, deep=False):
|
||
|
if node in self.constructed_objects:
|
||
|
return self.constructed_objects[node]
|
||
|
if deep:
|
||
|
old_deep = self.deep_construct
|
||
|
self.deep_construct = True
|
||
|
constructor = None
|
||
|
tag_suffix = None
|
||
|
if node.tag in self.yaml_constructors:
|
||
|
constructor = self.yaml_constructors[node.tag]
|
||
|
else:
|
||
|
raise ConstructorError(None, None, 'no constructor for tag %s' % node.tag)
|
||
|
if tag_suffix is None:
|
||
|
data = constructor(self, node)
|
||
|
else:
|
||
|
data = constructor(self, tag_suffix, node)
|
||
|
if isinstance(data, types.GeneratorType):
|
||
|
generator = data
|
||
|
data = next(generator)
|
||
|
if self.deep_construct:
|
||
|
for dummy in generator:
|
||
|
pass
|
||
|
else:
|
||
|
self.state_generators.append(generator)
|
||
|
self.constructed_objects[node] = data
|
||
|
if deep:
|
||
|
self.deep_construct = old_deep
|
||
|
return data
|
||
|
|
||
|
@marked
|
||
|
def construct_scalar(self, node):
|
||
|
if not isinstance(node, nodes.ScalarNode):
|
||
|
raise ConstructorError(
|
||
|
None, None,
|
||
|
'expected a scalar node, but found %s' % node.id,
|
||
|
node.start_mark
|
||
|
)
|
||
|
return node.value
|
||
|
|
||
|
def construct_sequence(self, node, deep=False):
|
||
|
if not isinstance(node, nodes.SequenceNode):
|
||
|
raise ConstructorError(
|
||
|
None, None,
|
||
|
'expected a sequence node, but found %s' % node.id,
|
||
|
node.start_mark
|
||
|
)
|
||
|
return [
|
||
|
self.construct_object(child, deep=deep)
|
||
|
for child in node.value
|
||
|
]
|
||
|
|
||
|
@marked
|
||
|
def construct_mapping(self, node, deep=False):
|
||
|
if not isinstance(node, nodes.MappingNode):
|
||
|
raise ConstructorError(
|
||
|
None, None,
|
||
|
'expected a mapping node, but found %s' % node.id,
|
||
|
node.start_mark
|
||
|
)
|
||
|
mapping = {}
|
||
|
for key_node, value_node in node.value:
|
||
|
key = self.construct_object(key_node, deep=deep)
|
||
|
if not isinstance(key, collections.Hashable):
|
||
|
self.echoerr(
|
||
|
'While constructing a mapping', node.start_mark,
|
||
|
'found unhashable key', key_node.start_mark
|
||
|
)
|
||
|
continue
|
||
|
elif type(key.value) != unicode:
|
||
|
self.echoerr(
|
||
|
'Error while constructing a mapping', node.start_mark,
|
||
|
'found key that is not a string', key_node.start_mark
|
||
|
)
|
||
|
continue
|
||
|
elif key in mapping:
|
||
|
self.echoerr(
|
||
|
'Error while constructing a mapping', node.start_mark,
|
||
|
'found duplicate key', key_node.start_mark
|
||
|
)
|
||
|
continue
|
||
|
value = self.construct_object(value_node, deep=deep)
|
||
|
mapping[key] = value
|
||
|
return mapping
|
||
|
|
||
|
@classmethod
|
||
|
def add_constructor(cls, tag, constructor):
|
||
|
if 'yaml_constructors' not in cls.__dict__:
|
||
|
cls.yaml_constructors = cls.yaml_constructors.copy()
|
||
|
cls.yaml_constructors[tag] = constructor
|
||
|
|
||
|
|
||
|
class Constructor(BaseConstructor):
|
||
|
def construct_scalar(self, node):
|
||
|
if isinstance(node, nodes.MappingNode):
|
||
|
for key_node, value_node in node.value:
|
||
|
if key_node.tag == 'tag:yaml.org,2002:value':
|
||
|
return self.construct_scalar(value_node)
|
||
|
return BaseConstructor.construct_scalar(self, node)
|
||
|
|
||
|
def flatten_mapping(self, node):
|
||
|
merge = []
|
||
|
index = 0
|
||
|
while index < len(node.value):
|
||
|
key_node, value_node = node.value[index]
|
||
|
if key_node.tag == 'tag:yaml.org,2002:merge':
|
||
|
del node.value[index]
|
||
|
if isinstance(value_node, nodes.MappingNode):
|
||
|
self.flatten_mapping(value_node)
|
||
|
merge.extend(value_node.value)
|
||
|
elif isinstance(value_node, nodes.SequenceNode):
|
||
|
submerge = []
|
||
|
for subnode in value_node.value:
|
||
|
if not isinstance(subnode, nodes.MappingNode):
|
||
|
raise ConstructorError(
|
||
|
'while constructing a mapping',
|
||
|
node.start_mark,
|
||
|
'expected a mapping for merging, but found %s' % subnode.id,
|
||
|
subnode.start_mark
|
||
|
)
|
||
|
self.flatten_mapping(subnode)
|
||
|
submerge.append(subnode.value)
|
||
|
submerge.reverse()
|
||
|
for value in submerge:
|
||
|
merge.extend(value)
|
||
|
else:
|
||
|
raise ConstructorError(
|
||
|
'while constructing a mapping',
|
||
|
node.start_mark,
|
||
|
('expected a mapping or list of mappings for merging, but found %s' % value_node.id),
|
||
|
value_node.start_mark
|
||
|
)
|
||
|
elif key_node.tag == 'tag:yaml.org,2002:value':
|
||
|
key_node.tag = 'tag:yaml.org,2002:str'
|
||
|
index += 1
|
||
|
else:
|
||
|
index += 1
|
||
|
if merge:
|
||
|
node.value = merge + node.value
|
||
|
|
||
|
def construct_mapping(self, node, deep=False):
|
||
|
if isinstance(node, nodes.MappingNode):
|
||
|
self.flatten_mapping(node)
|
||
|
return BaseConstructor.construct_mapping(self, node, deep=deep)
|
||
|
|
||
|
@marked
|
||
|
def construct_yaml_null(self, node):
|
||
|
self.construct_scalar(node)
|
||
|
return None
|
||
|
|
||
|
@marked
|
||
|
def construct_yaml_bool(self, node):
|
||
|
value = self.construct_scalar(node).value
|
||
|
return bool(value)
|
||
|
|
||
|
@marked
|
||
|
def construct_yaml_int(self, node):
|
||
|
value = self.construct_scalar(node).value
|
||
|
sign = +1
|
||
|
if value[0] == '-':
|
||
|
sign = -1
|
||
|
if value[0] in '+-':
|
||
|
value = value[1:]
|
||
|
if value == '0':
|
||
|
return 0
|
||
|
else:
|
||
|
return sign * int(value)
|
||
|
|
||
|
@marked
|
||
|
def construct_yaml_float(self, node):
|
||
|
value = self.construct_scalar(node).value
|
||
|
sign = +1
|
||
|
if value[0] == '-':
|
||
|
sign = -1
|
||
|
if value[0] in '+-':
|
||
|
value = value[1:]
|
||
|
else:
|
||
|
return sign * float(value)
|
||
|
|
||
|
def construct_yaml_str(self, node):
|
||
|
return self.construct_scalar(node)
|
||
|
|
||
|
def construct_yaml_seq(self, node):
|
||
|
data = gen_marked_value([], node.start_mark)
|
||
|
yield data
|
||
|
data.extend(self.construct_sequence(node))
|
||
|
|
||
|
def construct_yaml_map(self, node):
|
||
|
data = gen_marked_value({}, node.start_mark)
|
||
|
yield data
|
||
|
value = self.construct_mapping(node)
|
||
|
data.update(value)
|
||
|
|
||
|
def construct_undefined(self, node):
|
||
|
raise ConstructorError(
|
||
|
None, None,
|
||
|
'could not determine a constructor for the tag %r' % node.tag,
|
||
|
node.start_mark
|
||
|
)
|
||
|
|
||
|
|
||
|
Constructor.add_constructor(
|
||
|
'tag:yaml.org,2002:null', Constructor.construct_yaml_null)
|
||
|
|
||
|
Constructor.add_constructor(
|
||
|
'tag:yaml.org,2002:bool', Constructor.construct_yaml_bool)
|
||
|
|
||
|
Constructor.add_constructor(
|
||
|
'tag:yaml.org,2002:int', Constructor.construct_yaml_int)
|
||
|
|
||
|
Constructor.add_constructor(
|
||
|
'tag:yaml.org,2002:float', Constructor.construct_yaml_float)
|
||
|
|
||
|
Constructor.add_constructor(
|
||
|
'tag:yaml.org,2002:str', Constructor.construct_yaml_str)
|
||
|
|
||
|
Constructor.add_constructor(
|
||
|
'tag:yaml.org,2002:seq', Constructor.construct_yaml_seq)
|
||
|
|
||
|
Constructor.add_constructor(
|
||
|
'tag:yaml.org,2002:map', Constructor.construct_yaml_map)
|
||
|
|
||
|
Constructor.add_constructor(
|
||
|
None, Constructor.construct_undefined)
|