Constructor is done.
diff --git a/lib/yaml/constructor.py b/lib/yaml/constructor.py index 352709b..90d9152 100644 --- a/lib/yaml/constructor.py +++ b/lib/yaml/constructor.py
@@ -13,7 +13,7 @@ except NameError: from sets import Set as set -import binascii +import binascii, re class ConstructorError(MarkedYAMLError): pass @@ -83,18 +83,52 @@ "expected a mapping node, but found %s" % node.id, node.start_marker) mapping = {} + merge = None for key_node in node.value: - key = self.construct_object(key_node) - try: - duplicate_key = key in mapping - except TypeError, exc: - raise ConstructorError("while constructing a mapping", node.start_marker, - "found unacceptable key (%s)" % exc, key_node.start_marker) - if duplicate_key: - raise ConstructorError("while constructing a mapping", node.start_marker, - "found duplicate key", key_node.start_marker) - value = self.construct_object(node.value[key_node]) - mapping[key] = value + if key_node.tag == u'tag:yaml.org,2002:merge': + if merge is not None: + raise ConstructorError("while constructing a mapping", node.start_marker, + "found duplicate merge key", key_node.start_marker) + value_node = node.value[key_node] + if isinstance(value_node, MappingNode): + merge = [self.construct_mapping(value_node)] + elif isinstance(value_node, SequenceNode): + merge = [] + for subnode in value_node.value: + if not isinstance(subnode, MappingNode): + raise ConstructorError("while constructing a mapping", + node.start_marker, + "expected a mapping for merging, but found %s" + % subnode.id, subnode.start_marker) + merge.append(self.construct_mapping(subnode)) + merge.reverse() + else: + raise ConstructorError("while constructing a mapping", node.start_marker, + "expected a mapping or list of mappings for merging, but found %s" + % value_node.id, value_node.start_marker) + elif key_node.tag == u'tag:yaml.org,2002:value': + if '=' in mapping: + raise ConstructorError("while construction a mapping", node.start_marker, + "found duplicate value key", key_node.start_marker) + value = self.construct_object(node.value[key_node]) + mapping['='] = value + else: + key = self.construct_object(key_node) + try: + duplicate_key = key in mapping + except TypeError, exc: + raise ConstructorError("while constructing a mapping", node.start_marker, + "found unacceptable key (%s)" % exc, key_node.start_marker) + if duplicate_key: + raise ConstructorError("while constructing a mapping", node.start_marker, + "found duplicate key", key_node.start_marker) + value = self.construct_object(node.value[key_node]) + mapping[key] = value + if merge is not None: + merge.append(mapping) + mapping = {} + for submapping in merge: + mapping.update(submapping) return mapping def construct_pairs(self, node): @@ -201,6 +235,82 @@ raise ConstructorError(None, None, "failed to decode base64 data: %s" % exc, node.start_mark) + timestamp_regexp = re.compile( + ur'''^(?P<year>[0-9][0-9][0-9][0-9]) + -(?P<month>[0-9][0-9]?) + -(?P<day>[0-9][0-9]?) + (?:[Tt]|[ \t]+) + (?P<hour>[0-9][0-9]?) + :(?P<minute>[0-9][0-9]) + :(?P<second>[0-9][0-9]) + (?:\.(?P<fraction>[0-9]*))? + (?:[ \t]*(?:Z|(?P<tz_hour>[-+][0-9][0-9]?) + (?::(?P<tz_minute>[0-9][0-9])?)))?$''', re.X), + + def construct_yaml_timestamp(self, node): + value = self.construct_scalar(node) + match = self.timestamp_expr.match(node.value) + values = match.groupdict() + for key in values: + if values[key]: + values[key] = int(values[key]) + else: + values[key] = 0 + fraction = values['fraction'] + if micro: + while 10*fraction < 1000000: + fraction *= 10 + values['fraction'] = fraction + stamp = datetime.datetime(values['year'], values['month'], values['day'], + values['hour'], values['minute'], values['second'], values['fraction']) + diff = datetime.timedelta(hours=values['tz_hour'], minutes=values['tz_minute']) + return stamp-diff + + def construct_yaml_omap(self, node): + # Note: we do not check for duplicate keys, because it's too + # CPU-expensive. + if not isinstance(node, SequenceNode): + raise ConstructorError("while constructing an ordered map", node.start_marker, + "expected a sequence, but found %s" % node.id, node.start_marker) + omap = [] + for subnode in node.value: + if not isinstance(subnode, MappingNode): + raise ConstructorError("while constructing an ordered map", node.start_marker, + "expected a mapping of length 1, but found %s" % subnode.id, + subnode.start_marker) + if len(subnode.value) != 1: + raise ConstructorError("while constructing an ordered map", node.start_marker, + "expected a single mapping item, but found %d items" % len(subnode.value), + subnode.start_marker) + key_node = subnode.value.keys()[0] + key = self.construct_object(key_node) + value = self.construct_object(subnode.value[key_node]) + omap.append((key, value)) + + def construct_yaml_pairs(self, node): + # Note: the same code as `construct_yaml_omap`. + if not isinstance(node, SequenceNode): + raise ConstructorError("while constructing pairs", node.start_marker, + "expected a sequence, but found %s" % node.id, node.start_marker) + omap = [] + for subnode in node.value: + if not isinstance(subnode, MappingNode): + raise ConstructorError("while constructing pairs", node.start_marker, + "expected a mapping of length 1, but found %s" % subnode.id, + subnode.start_marker) + if len(subnode.value) != 1: + raise ConstructorError("while constructing pairs", node.start_marker, + "expected a single mapping item, but found %d items" % len(subnode.value), + subnode.start_marker) + key_node = subnode.value.keys()[0] + key = self.construct_object(key_node) + value = self.construct_object(subnode.value[key_node]) + omap.append((key, value)) + + def construct_yaml_set(self, node): + value = self.construct_mapping(node) + return set(value) + def construct_yaml_str(self, node): value = self.construct_scalar(node) try: @@ -208,6 +318,12 @@ except UnicodeEncodeError: return value + def construct_yaml_seq(self, node): + return self.construct_sequence(node) + + def construct_yaml_map(self, node): + return self.construct_mapping(node) + Constructor.add_constructor( u'tag:yaml.org,2002:null', Constructor.construct_yaml_null) @@ -225,9 +341,33 @@ Constructor.construct_yaml_float) Constructor.add_constructor( + u'tag:yaml.org,2002:timestamp', + Constructor.construct_yaml_timestamp) + +Constructor.add_constructor( + u'tag:yaml.org,2002:omap', + Constructor.construct_yaml_omap) + +Constructor.add_constructor( + u'tag:yaml.org,2002:pairs', + Constructor.construct_yaml_pairs) + +Constructor.add_constructor( + u'tag:yaml.org,2002:set', + Constructor.construct_yaml_set) + +Constructor.add_constructor( u'tag:yaml.org,2002:str', Constructor.construct_yaml_str) +Constructor.add_constructor( + u'tag:yaml.org,2002:seq', + Constructor.construct_yaml_seq) + +Constructor.add_constructor( + u'tag:yaml.org,2002:map', + Constructor.construct_yaml_map) + class YAMLObjectMetaclass(type): def __init__(cls, name, bases, kwds):
diff --git a/lib/yaml/resolver.py b/lib/yaml/resolver.py index e4d1d8d..749a018 100644 --- a/lib/yaml/resolver.py +++ b/lib/yaml/resolver.py
@@ -134,3 +134,10 @@ re.compile(ur'^(?:=)$'), ['=']) +# The following detector is only for documentation purposes. It cannot work +# because plain scalars cannot start with '!', '&', or '*'. +Resolver.add_detector( + u'tag:yaml.org,2002:yaml', + re.compile(ur'^(?:!|&|\*)$'), + list(u'!&*')) +
diff --git a/tests/data/spec-08-10.data b/tests/data/spec-08-10.data index 285e462..72acc56 100644 --- a/tests/data/spec-08-10.data +++ b/tests/data/spec-08-10.data
@@ -6,7 +6,7 @@ folded: > This sentence is false. - collections: !!seq + collections: !!map sequence: !!seq # Entry: - entry # Plain # Mapping entry:
diff --git a/tests/data/yaml.data b/tests/data/yaml.data new file mode 100644 index 0000000..a4bb3f8 --- /dev/null +++ b/tests/data/yaml.data
@@ -0,0 +1,3 @@ +- !!yaml '!' +- !!yaml '&' +- !!yaml '*'
diff --git a/tests/data/yaml.detect b/tests/data/yaml.detect new file mode 100644 index 0000000..e2cf189 --- /dev/null +++ b/tests/data/yaml.detect
@@ -0,0 +1 @@ +tag:yaml.org,2002:yaml