summaryrefslogtreecommitdiff
path: root/morphlib/yamlparse.py
blob: 9959961807b61f90e2d5f2fe5665ba0185ec011c (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
# Copyright (C) 2013  Codethink Limited
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; version 2 of the License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.


import morphlib
from morphlib.util import OrderedDict

if morphlib.got_yaml: # pragma: no cover
    yaml = morphlib.yaml


if morphlib.got_yaml: # pragma: no cover

    class OrderedDictYAMLLoader(yaml.SafeLoader):
        """A YAML loader that loads mappings into ordered dictionaries.

       When YAML is loaded with this Loader, it loads mappings as ordered
       dictionaries, so the order the keys were written in is maintained.

       When combined with the OrderedDictYAMLDumper, this allows yaml documents
       to be written out in a similar format to they were read.

        """

        def __init__(self, *args, **kwargs):
            yaml.SafeLoader.__init__(self, *args, **kwargs)

            # When YAML encounters a mapping (which YAML identifies with
            # the given tag), it will use construct_yaml_map to read it as
            # an OrderedDict.
            self.add_constructor(u'tag:yaml.org,2002:map',
                                 type(self).construct_yaml_map)

        def construct_yaml_map(self, node):
            data = OrderedDict()
            yield data
            value = self.construct_mapping(node)
            data.update(value)

        def construct_mapping(self, node, deep=False):
            if isinstance(node, yaml.MappingNode):
                self.flatten_mapping(node)
            else:
                raise yaml.constructor.ConstructorError(
                    None, None,
                    'expected a mapping node, but found %s' % node.id,
                    node.start_mark)

            mapping = OrderedDict()
            for key_node, value_node in node.value:
                key = self.construct_object(key_node, deep=deep)
                try:
                    hash(key)
                except TypeError, exc:
                    raise yaml.constructor.ConstructorError(
                        'while constructing a mapping', node.start_mark,
                        'found unacceptable key (%s)' % exc,
                        key_node.start_mark)
                value = self.construct_object(value_node, deep=deep)
                mapping[key] = value
            return mapping

    class OrderedDictYAMLDumper(yaml.SafeDumper):
        """A YAML dumper that will dump OrderedDicts as mappings.

       When YAML is dumped with this Dumper, it dumps OrderedDicts as
       mappings, preserving the key order, so the order the keys were
       written in is maintained.

       When combined with the OrderedDictYAMLDumper, this allows yaml documents
       to be written out in a similar format to they were read.

        """

        def __init__(self, *args, **kwargs):
            yaml.SafeDumper.__init__(self, *args, **kwargs)

            # When YAML sees an OrderedDict, use represent_ordered_dict to
            # dump it
            self.add_representer(OrderedDict,
                                 type(self).represent_ordered_dict)

        def represent_ordered_dict(self, odict):
            return self.represent_ordered_mapping(
                u'tag:yaml.org,2002:map', odict)

        def represent_ordered_mapping(self, tag, omap):
            value = []
            node = yaml.MappingNode(tag, value)
            if self.alias_key is not None:
                self.represented_objects[self.alias_key] = node
            best_style = True
            for item_key, item_value in omap.iteritems():
                node_key = self.represent_data(item_key)
                node_value = self.represent_data(item_value)
                if not (isinstance(node_key, yaml.ScalarNode) and
                        not node_key.style):
                    best_style = False # pragma: no cover
                if not (isinstance(node_value, yaml.ScalarNode) and
                        not node_value.style):
                    best_style = False # pragma: no cover
                value.append((node_key, node_value))
            if self.default_flow_style is not None:
                node.flow_style = self.default_flow_style
            else:
                node.flow_style = best_style # pragma: no cover
            return node

    def load(*args, **kwargs):
        return yaml.load(Loader=OrderedDictYAMLLoader, *args, **kwargs)

    def dump(*args, **kwargs):
        if 'default_flow_style' not in kwargs:
            kwargs['default_flow_style'] = False
        return yaml.dump(Dumper=OrderedDictYAMLDumper, *args, **kwargs)

else: # pragma: no cover
    def load(*args, **kwargs):
        raise morphlib.Error('YAML not available')
    def dump(*args, **kwargs):
        raise morphlib.Error('YAML not available')