1# coding: utf-8
2
3from __future__ import print_function, absolute_import, division
4
5
6from ruamel.yaml.error import *  # NOQA
7from ruamel.yaml.nodes import *  # NOQA
8from ruamel.yaml.compat import text_type, binary_type, to_unicode, PY2, PY3
9from ruamel.yaml.compat import ordereddict  # type: ignore
10from ruamel.yaml.compat import nprint, nprintf  # NOQA
11from ruamel.yaml.scalarstring import (
12    LiteralScalarString,
13    FoldedScalarString,
14    SingleQuotedScalarString,
15    DoubleQuotedScalarString,
16    PlainScalarString,
17)
18from ruamel.yaml.comments import (
19    CommentedMap,
20    CommentedOrderedMap,
21    CommentedSeq,
22    CommentedKeySeq,
23    CommentedKeyMap,
24    CommentedSet,
25    comment_attrib,
26    merge_attrib,
27    TaggedScalar,
28)
29from ruamel.yaml.scalarint import ScalarInt, BinaryInt, OctalInt, HexInt, HexCapsInt
30from ruamel.yaml.scalarfloat import ScalarFloat
31from ruamel.yaml.scalarbool import ScalarBoolean
32from ruamel.yaml.timestamp import TimeStamp
33
34import datetime
35import sys
36import types
37
38if PY3:
39    import copyreg
40    import base64
41else:
42    import copy_reg as copyreg  # type: ignore
43
44if False:  # MYPY
45    from typing import Dict, List, Any, Union, Text, Optional  # NOQA
46
47# fmt: off
48__all__ = ['BaseRepresenter', 'SafeRepresenter', 'Representer',
49           'RepresenterError', 'RoundTripRepresenter']
50# fmt: on
51
52
53class RepresenterError(YAMLError):
54    pass
55
56
57if PY2:
58
59    def get_classobj_bases(cls):
60        # type: (Any) -> Any
61        bases = [cls]
62        for base in cls.__bases__:
63            bases.extend(get_classobj_bases(base))
64        return bases
65
66
67class BaseRepresenter(object):
68
69    yaml_representers = {}  # type: Dict[Any, Any]
70    yaml_multi_representers = {}  # type: Dict[Any, Any]
71
72    def __init__(self, default_style=None, default_flow_style=None, dumper=None):
73        # type: (Any, Any, Any, Any) -> None
74        self.dumper = dumper
75        if self.dumper is not None:
76            self.dumper._representer = self
77        self.default_style = default_style
78        self.default_flow_style = default_flow_style
79        self.represented_objects = {}  # type: Dict[Any, Any]
80        self.object_keeper = []  # type: List[Any]
81        self.alias_key = None  # type: Optional[int]
82        self.sort_base_mapping_type_on_output = True
83
84    @property
85    def serializer(self):
86        # type: () -> Any
87        try:
88            if hasattr(self.dumper, 'typ'):
89                return self.dumper.serializer
90            return self.dumper._serializer
91        except AttributeError:
92            return self  # cyaml
93
94    def represent(self, data):
95        # type: (Any) -> None
96        node = self.represent_data(data)
97        self.serializer.serialize(node)
98        self.represented_objects = {}
99        self.object_keeper = []
100        self.alias_key = None
101
102    def represent_data(self, data):
103        # type: (Any) -> Any
104        if self.ignore_aliases(data):
105            self.alias_key = None
106        else:
107            self.alias_key = id(data)
108        if self.alias_key is not None:
109            if self.alias_key in self.represented_objects:
110                node = self.represented_objects[self.alias_key]
111                # if node is None:
112                #     raise RepresenterError(
113                #          "recursive objects are not allowed: %r" % data)
114                return node
115            # self.represented_objects[alias_key] = None
116            self.object_keeper.append(data)
117        data_types = type(data).__mro__
118        if PY2:
119            # if type(data) is types.InstanceType:
120            if isinstance(data, types.InstanceType):
121                data_types = get_classobj_bases(data.__class__) + list(data_types)
122        if data_types[0] in self.yaml_representers:
123            node = self.yaml_representers[data_types[0]](self, data)
124        else:
125            for data_type in data_types:
126                if data_type in self.yaml_multi_representers:
127                    node = self.yaml_multi_representers[data_type](self, data)
128                    break
129            else:
130                if None in self.yaml_multi_representers:
131                    node = self.yaml_multi_representers[None](self, data)
132                elif None in self.yaml_representers:
133                    node = self.yaml_representers[None](self, data)
134                else:
135                    node = ScalarNode(None, text_type(data))
136        # if alias_key is not None:
137        #     self.represented_objects[alias_key] = node
138        return node
139
140    def represent_key(self, data):
141        # type: (Any) -> Any
142        """
143        David Fraser: Extract a method to represent keys in mappings, so that
144        a subclass can choose not to quote them (for example)
145        used in represent_mapping
146        https://bitbucket.org/davidfraser/pyyaml/commits/d81df6eb95f20cac4a79eed95ae553b5c6f77b8c
147        """
148        return self.represent_data(data)
149
150    @classmethod
151    def add_representer(cls, data_type, representer):
152        # type: (Any, Any) -> None
153        if 'yaml_representers' not in cls.__dict__:
154            cls.yaml_representers = cls.yaml_representers.copy()
155        cls.yaml_representers[data_type] = representer
156
157    @classmethod
158    def add_multi_representer(cls, data_type, representer):
159        # type: (Any, Any) -> None
160        if 'yaml_multi_representers' not in cls.__dict__:
161            cls.yaml_multi_representers = cls.yaml_multi_representers.copy()
162        cls.yaml_multi_representers[data_type] = representer
163
164    def represent_scalar(self, tag, value, style=None, anchor=None):
165        # type: (Any, Any, Any, Any) -> Any
166        if style is None:
167            style = self.default_style
168        comment = None
169        if style and style[0] in '|>':
170            comment = getattr(value, 'comment', None)
171            if comment:
172                comment = [None, [comment]]
173        node = ScalarNode(tag, value, style=style, comment=comment, anchor=anchor)
174        if self.alias_key is not None:
175            self.represented_objects[self.alias_key] = node
176        return node
177
178    def represent_sequence(self, tag, sequence, flow_style=None):
179        # type: (Any, Any, Any) -> Any
180        value = []  # type: List[Any]
181        node = SequenceNode(tag, value, flow_style=flow_style)
182        if self.alias_key is not None:
183            self.represented_objects[self.alias_key] = node
184        best_style = True
185        for item in sequence:
186            node_item = self.represent_data(item)
187            if not (isinstance(node_item, ScalarNode) and not node_item.style):
188                best_style = False
189            value.append(node_item)
190        if flow_style is None:
191            if self.default_flow_style is not None:
192                node.flow_style = self.default_flow_style
193            else:
194                node.flow_style = best_style
195        return node
196
197    def represent_omap(self, tag, omap, flow_style=None):
198        # type: (Any, Any, Any) -> Any
199        value = []  # type: List[Any]
200        node = SequenceNode(tag, value, flow_style=flow_style)
201        if self.alias_key is not None:
202            self.represented_objects[self.alias_key] = node
203        best_style = True
204        for item_key in omap:
205            item_val = omap[item_key]
206            node_item = self.represent_data({item_key: item_val})
207            # if not (isinstance(node_item, ScalarNode) \
208            #    and not node_item.style):
209            #     best_style = False
210            value.append(node_item)
211        if flow_style is None:
212            if self.default_flow_style is not None:
213                node.flow_style = self.default_flow_style
214            else:
215                node.flow_style = best_style
216        return node
217
218    def represent_mapping(self, tag, mapping, flow_style=None):
219        # type: (Any, Any, Any) -> Any
220        value = []  # type: List[Any]
221        node = MappingNode(tag, value, flow_style=flow_style)
222        if self.alias_key is not None:
223            self.represented_objects[self.alias_key] = node
224        best_style = True
225        if hasattr(mapping, 'items'):
226            mapping = list(mapping.items())
227            if self.sort_base_mapping_type_on_output:
228                try:
229                    mapping = sorted(mapping)
230                except TypeError:
231                    pass
232        for item_key, item_value in mapping:
233            node_key = self.represent_key(item_key)
234            node_value = self.represent_data(item_value)
235            if not (isinstance(node_key, ScalarNode) and not node_key.style):
236                best_style = False
237            if not (isinstance(node_value, ScalarNode) and not node_value.style):
238                best_style = False
239            value.append((node_key, node_value))
240        if flow_style is None:
241            if self.default_flow_style is not None:
242                node.flow_style = self.default_flow_style
243            else:
244                node.flow_style = best_style
245        return node
246
247    def ignore_aliases(self, data):
248        # type: (Any) -> bool
249        return False
250
251
252class SafeRepresenter(BaseRepresenter):
253    def ignore_aliases(self, data):
254        # type: (Any) -> bool
255        # https://docs.python.org/3/reference/expressions.html#parenthesized-forms :
256        # "i.e. two occurrences of the empty tuple may or may not yield the same object"
257        # so "data is ()" should not be used
258        if data is None or (isinstance(data, tuple) and data == ()):
259            return True
260        if isinstance(data, (binary_type, text_type, bool, int, float)):
261            return True
262        return False
263
264    def represent_none(self, data):
265        # type: (Any) -> Any
266        return self.represent_scalar(u'tag:yaml.org,2002:null', u'null')
267
268    if PY3:
269
270        def represent_str(self, data):
271            # type: (Any) -> Any
272            return self.represent_scalar(u'tag:yaml.org,2002:str', data)
273
274        def represent_binary(self, data):
275            # type: (Any) -> Any
276            if hasattr(base64, 'encodebytes'):
277                data = base64.encodebytes(data).decode('ascii')
278            else:
279                data = base64.encodestring(data).decode('ascii')
280            return self.represent_scalar(u'tag:yaml.org,2002:binary', data, style='|')
281
282    else:
283
284        def represent_str(self, data):
285            # type: (Any) -> Any
286            tag = None
287            style = None
288            try:
289                data = unicode(data, 'ascii')
290                tag = u'tag:yaml.org,2002:str'
291            except UnicodeDecodeError:
292                try:
293                    data = unicode(data, 'utf-8')
294                    tag = u'tag:yaml.org,2002:str'
295                except UnicodeDecodeError:
296                    data = data.encode('base64')
297                    tag = u'tag:yaml.org,2002:binary'
298                    style = '|'
299            return self.represent_scalar(tag, data, style=style)
300
301        def represent_unicode(self, data):
302            # type: (Any) -> Any
303            return self.represent_scalar(u'tag:yaml.org,2002:str', data)
304
305    def represent_bool(self, data, anchor=None):
306        # type: (Any, Optional[Any]) -> Any
307        try:
308            value = self.dumper.boolean_representation[bool(data)]
309        except AttributeError:
310            if data:
311                value = u'true'
312            else:
313                value = u'false'
314        return self.represent_scalar(u'tag:yaml.org,2002:bool', value, anchor=anchor)
315
316    def represent_int(self, data):
317        # type: (Any) -> Any
318        return self.represent_scalar(u'tag:yaml.org,2002:int', text_type(data))
319
320    if PY2:
321
322        def represent_long(self, data):
323            # type: (Any) -> Any
324            return self.represent_scalar(u'tag:yaml.org,2002:int', text_type(data))
325
326    inf_value = 1e300
327    while repr(inf_value) != repr(inf_value * inf_value):
328        inf_value *= inf_value
329
330    def represent_float(self, data):
331        # type: (Any) -> Any
332        if data != data or (data == 0.0 and data == 1.0):
333            value = u'.nan'
334        elif data == self.inf_value:
335            value = u'.inf'
336        elif data == -self.inf_value:
337            value = u'-.inf'
338        else:
339            value = to_unicode(repr(data)).lower()
340            if getattr(self.serializer, 'use_version', None) == (1, 1):
341                if u'.' not in value and u'e' in value:
342                    # Note that in some cases `repr(data)` represents a float number
343                    # without the decimal parts.  For instance:
344                    #   >>> repr(1e17)
345                    #   '1e17'
346                    # Unfortunately, this is not a valid float representation according
347                    # to the definition of the `!!float` tag in YAML 1.1.  We fix
348                    # this by adding '.0' before the 'e' symbol.
349                    value = value.replace(u'e', u'.0e', 1)
350        return self.represent_scalar(u'tag:yaml.org,2002:float', value)
351
352    def represent_list(self, data):
353        # type: (Any) -> Any
354        # pairs = (len(data) > 0 and isinstance(data, list))
355        # if pairs:
356        #     for item in data:
357        #         if not isinstance(item, tuple) or len(item) != 2:
358        #             pairs = False
359        #             break
360        # if not pairs:
361        return self.represent_sequence(u'tag:yaml.org,2002:seq', data)
362
363    # value = []
364    # for item_key, item_value in data:
365    #     value.append(self.represent_mapping(u'tag:yaml.org,2002:map',
366    #         [(item_key, item_value)]))
367    # return SequenceNode(u'tag:yaml.org,2002:pairs', value)
368
369    def represent_dict(self, data):
370        # type: (Any) -> Any
371        return self.represent_mapping(u'tag:yaml.org,2002:map', data)
372
373    def represent_ordereddict(self, data):
374        # type: (Any) -> Any
375        return self.represent_omap(u'tag:yaml.org,2002:omap', data)
376
377    def represent_set(self, data):
378        # type: (Any) -> Any
379        value = {}  # type: Dict[Any, None]
380        for key in data:
381            value[key] = None
382        return self.represent_mapping(u'tag:yaml.org,2002:set', value)
383
384    def represent_date(self, data):
385        # type: (Any) -> Any
386        value = to_unicode(data.isoformat())
387        return self.represent_scalar(u'tag:yaml.org,2002:timestamp', value)
388
389    def represent_datetime(self, data):
390        # type: (Any) -> Any
391        value = to_unicode(data.isoformat(' '))
392        return self.represent_scalar(u'tag:yaml.org,2002:timestamp', value)
393
394    def represent_yaml_object(self, tag, data, cls, flow_style=None):
395        # type: (Any, Any, Any, Any) -> Any
396        if hasattr(data, '__getstate__'):
397            state = data.__getstate__()
398        else:
399            state = data.__dict__.copy()
400        return self.represent_mapping(tag, state, flow_style=flow_style)
401
402    def represent_undefined(self, data):
403        # type: (Any) -> None
404        raise RepresenterError('cannot represent an object: %s' % (data,))
405
406
407SafeRepresenter.add_representer(type(None), SafeRepresenter.represent_none)
408
409SafeRepresenter.add_representer(str, SafeRepresenter.represent_str)
410
411if PY2:
412    SafeRepresenter.add_representer(unicode, SafeRepresenter.represent_unicode)
413else:
414    SafeRepresenter.add_representer(bytes, SafeRepresenter.represent_binary)
415
416SafeRepresenter.add_representer(bool, SafeRepresenter.represent_bool)
417
418SafeRepresenter.add_representer(int, SafeRepresenter.represent_int)
419
420if PY2:
421    SafeRepresenter.add_representer(long, SafeRepresenter.represent_long)
422
423SafeRepresenter.add_representer(float, SafeRepresenter.represent_float)
424
425SafeRepresenter.add_representer(list, SafeRepresenter.represent_list)
426
427SafeRepresenter.add_representer(tuple, SafeRepresenter.represent_list)
428
429SafeRepresenter.add_representer(dict, SafeRepresenter.represent_dict)
430
431SafeRepresenter.add_representer(set, SafeRepresenter.represent_set)
432
433SafeRepresenter.add_representer(ordereddict, SafeRepresenter.represent_ordereddict)
434
435if sys.version_info >= (2, 7):
436    import collections
437
438    SafeRepresenter.add_representer(
439        collections.OrderedDict, SafeRepresenter.represent_ordereddict
440    )
441
442SafeRepresenter.add_representer(datetime.date, SafeRepresenter.represent_date)
443
444SafeRepresenter.add_representer(datetime.datetime, SafeRepresenter.represent_datetime)
445
446SafeRepresenter.add_representer(None, SafeRepresenter.represent_undefined)
447
448
449class Representer(SafeRepresenter):
450    if PY2:
451
452        def represent_str(self, data):
453            # type: (Any) -> Any
454            tag = None
455            style = None
456            try:
457                data = unicode(data, 'ascii')
458                tag = u'tag:yaml.org,2002:str'
459            except UnicodeDecodeError:
460                try:
461                    data = unicode(data, 'utf-8')
462                    tag = u'tag:yaml.org,2002:python/str'
463                except UnicodeDecodeError:
464                    data = data.encode('base64')
465                    tag = u'tag:yaml.org,2002:binary'
466                    style = '|'
467            return self.represent_scalar(tag, data, style=style)
468
469        def represent_unicode(self, data):
470            # type: (Any) -> Any
471            tag = None
472            try:
473                data.encode('ascii')
474                tag = u'tag:yaml.org,2002:python/unicode'
475            except UnicodeEncodeError:
476                tag = u'tag:yaml.org,2002:str'
477            return self.represent_scalar(tag, data)
478
479        def represent_long(self, data):
480            # type: (Any) -> Any
481            tag = u'tag:yaml.org,2002:int'
482            if int(data) is not data:
483                tag = u'tag:yaml.org,2002:python/long'
484            return self.represent_scalar(tag, to_unicode(data))
485
486    def represent_complex(self, data):
487        # type: (Any) -> Any
488        if data.imag == 0.0:
489            data = u'%r' % data.real
490        elif data.real == 0.0:
491            data = u'%rj' % data.imag
492        elif data.imag > 0:
493            data = u'%r+%rj' % (data.real, data.imag)
494        else:
495            data = u'%r%rj' % (data.real, data.imag)
496        return self.represent_scalar(u'tag:yaml.org,2002:python/complex', data)
497
498    def represent_tuple(self, data):
499        # type: (Any) -> Any
500        return self.represent_sequence(u'tag:yaml.org,2002:python/tuple', data)
501
502    def represent_name(self, data):
503        # type: (Any) -> Any
504        try:
505            name = u'%s.%s' % (data.__module__, data.__qualname__)
506        except AttributeError:
507            # probably PY2
508            name = u'%s.%s' % (data.__module__, data.__name__)
509        return self.represent_scalar(u'tag:yaml.org,2002:python/name:' + name, "")
510
511    def represent_module(self, data):
512        # type: (Any) -> Any
513        return self.represent_scalar(u'tag:yaml.org,2002:python/module:' + data.__name__, "")
514
515    if PY2:
516
517        def represent_instance(self, data):
518            # type: (Any) -> Any
519            # For instances of classic classes, we use __getinitargs__ and
520            # __getstate__ to serialize the data.
521
522            # If data.__getinitargs__ exists, the object must be reconstructed
523            # by calling cls(**args), where args is a tuple returned by
524            # __getinitargs__. Otherwise, the cls.__init__ method should never
525            # be called and the class instance is created by instantiating a
526            # trivial class and assigning to the instance's __class__ variable.
527
528            # If data.__getstate__ exists, it returns the state of the object.
529            # Otherwise, the state of the object is data.__dict__.
530
531            # We produce either a !!python/object or !!python/object/new node.
532            # If data.__getinitargs__ does not exist and state is a dictionary,
533            # we produce a !!python/object node . Otherwise we produce a
534            # !!python/object/new node.
535
536            cls = data.__class__
537            class_name = u'%s.%s' % (cls.__module__, cls.__name__)
538            args = None
539            state = None
540            if hasattr(data, '__getinitargs__'):
541                args = list(data.__getinitargs__())
542            if hasattr(data, '__getstate__'):
543                state = data.__getstate__()
544            else:
545                state = data.__dict__
546            if args is None and isinstance(state, dict):
547                return self.represent_mapping(
548                    u'tag:yaml.org,2002:python/object:' + class_name, state
549                )
550            if isinstance(state, dict) and not state:
551                return self.represent_sequence(
552                    u'tag:yaml.org,2002:python/object/new:' + class_name, args
553                )
554            value = {}
555            if bool(args):
556                value['args'] = args
557            value['state'] = state  # type: ignore
558            return self.represent_mapping(
559                u'tag:yaml.org,2002:python/object/new:' + class_name, value
560            )
561
562    def represent_object(self, data):
563        # type: (Any) -> Any
564        # We use __reduce__ API to save the data. data.__reduce__ returns
565        # a tuple of length 2-5:
566        #   (function, args, state, listitems, dictitems)
567
568        # For reconstructing, we calls function(*args), then set its state,
569        # listitems, and dictitems if they are not None.
570
571        # A special case is when function.__name__ == '__newobj__'. In this
572        # case we create the object with args[0].__new__(*args).
573
574        # Another special case is when __reduce__ returns a string - we don't
575        # support it.
576
577        # We produce a !!python/object, !!python/object/new or
578        # !!python/object/apply node.
579
580        cls = type(data)
581        if cls in copyreg.dispatch_table:
582            reduce = copyreg.dispatch_table[cls](data)
583        elif hasattr(data, '__reduce_ex__'):
584            reduce = data.__reduce_ex__(2)
585        elif hasattr(data, '__reduce__'):
586            reduce = data.__reduce__()
587        else:
588            raise RepresenterError('cannot represent object: %r' % (data,))
589        reduce = (list(reduce) + [None] * 5)[:5]
590        function, args, state, listitems, dictitems = reduce
591        args = list(args)
592        if state is None:
593            state = {}
594        if listitems is not None:
595            listitems = list(listitems)
596        if dictitems is not None:
597            dictitems = dict(dictitems)
598        if function.__name__ == '__newobj__':
599            function = args[0]
600            args = args[1:]
601            tag = u'tag:yaml.org,2002:python/object/new:'
602            newobj = True
603        else:
604            tag = u'tag:yaml.org,2002:python/object/apply:'
605            newobj = False
606        try:
607            function_name = u'%s.%s' % (function.__module__, function.__qualname__)
608        except AttributeError:
609            # probably PY2
610            function_name = u'%s.%s' % (function.__module__, function.__name__)
611        if not args and not listitems and not dictitems and isinstance(state, dict) and newobj:
612            return self.represent_mapping(
613                u'tag:yaml.org,2002:python/object:' + function_name, state
614            )
615        if not listitems and not dictitems and isinstance(state, dict) and not state:
616            return self.represent_sequence(tag + function_name, args)
617        value = {}
618        if args:
619            value['args'] = args
620        if state or not isinstance(state, dict):
621            value['state'] = state
622        if listitems:
623            value['listitems'] = listitems
624        if dictitems:
625            value['dictitems'] = dictitems
626        return self.represent_mapping(tag + function_name, value)
627
628
629if PY2:
630    Representer.add_representer(str, Representer.represent_str)
631
632    Representer.add_representer(unicode, Representer.represent_unicode)
633
634    Representer.add_representer(long, Representer.represent_long)
635
636Representer.add_representer(complex, Representer.represent_complex)
637
638Representer.add_representer(tuple, Representer.represent_tuple)
639
640Representer.add_representer(type, Representer.represent_name)
641
642if PY2:
643    Representer.add_representer(types.ClassType, Representer.represent_name)
644
645Representer.add_representer(types.FunctionType, Representer.represent_name)
646
647Representer.add_representer(types.BuiltinFunctionType, Representer.represent_name)
648
649Representer.add_representer(types.ModuleType, Representer.represent_module)
650
651if PY2:
652    Representer.add_multi_representer(types.InstanceType, Representer.represent_instance)
653
654Representer.add_multi_representer(object, Representer.represent_object)
655
656Representer.add_multi_representer(type, Representer.represent_name)
657
658
659class RoundTripRepresenter(SafeRepresenter):
660    # need to add type here and write out the .comment
661    # in serializer and emitter
662
663    def __init__(self, default_style=None, default_flow_style=None, dumper=None):
664        # type: (Any, Any, Any) -> None
665        if not hasattr(dumper, 'typ') and default_flow_style is None:
666            default_flow_style = False
667        SafeRepresenter.__init__(
668            self,
669            default_style=default_style,
670            default_flow_style=default_flow_style,
671            dumper=dumper,
672        )
673
674    def ignore_aliases(self, data):
675        # type: (Any) -> bool
676        try:
677            if data.anchor is not None and data.anchor.value is not None:
678                return False
679        except AttributeError:
680            pass
681        return SafeRepresenter.ignore_aliases(self, data)
682
683    def represent_none(self, data):
684        # type: (Any) -> Any
685        if len(self.represented_objects) == 0 and not self.serializer.use_explicit_start:
686            # this will be open ended (although it is not yet)
687            return self.represent_scalar(u'tag:yaml.org,2002:null', u'null')
688        return self.represent_scalar(u'tag:yaml.org,2002:null', "")
689
690    def represent_literal_scalarstring(self, data):
691        # type: (Any) -> Any
692        tag = None
693        style = '|'
694        anchor = data.yaml_anchor(any=True)
695        if PY2 and not isinstance(data, unicode):
696            data = unicode(data, 'ascii')
697        tag = u'tag:yaml.org,2002:str'
698        return self.represent_scalar(tag, data, style=style, anchor=anchor)
699
700    represent_preserved_scalarstring = represent_literal_scalarstring
701
702    def represent_folded_scalarstring(self, data):
703        # type: (Any) -> Any
704        tag = None
705        style = '>'
706        anchor = data.yaml_anchor(any=True)
707        for fold_pos in reversed(getattr(data, 'fold_pos', [])):
708            if (
709                data[fold_pos] == ' '
710                and (fold_pos > 0 and not data[fold_pos - 1].isspace())
711                and (fold_pos < len(data) and not data[fold_pos + 1].isspace())
712            ):
713                data = data[:fold_pos] + '\a' + data[fold_pos:]
714        if PY2 and not isinstance(data, unicode):
715            data = unicode(data, 'ascii')
716        tag = u'tag:yaml.org,2002:str'
717        return self.represent_scalar(tag, data, style=style, anchor=anchor)
718
719    def represent_single_quoted_scalarstring(self, data):
720        # type: (Any) -> Any
721        tag = None
722        style = "'"
723        anchor = data.yaml_anchor(any=True)
724        if PY2 and not isinstance(data, unicode):
725            data = unicode(data, 'ascii')
726        tag = u'tag:yaml.org,2002:str'
727        return self.represent_scalar(tag, data, style=style, anchor=anchor)
728
729    def represent_double_quoted_scalarstring(self, data):
730        # type: (Any) -> Any
731        tag = None
732        style = '"'
733        anchor = data.yaml_anchor(any=True)
734        if PY2 and not isinstance(data, unicode):
735            data = unicode(data, 'ascii')
736        tag = u'tag:yaml.org,2002:str'
737        return self.represent_scalar(tag, data, style=style, anchor=anchor)
738
739    def represent_plain_scalarstring(self, data):
740        # type: (Any) -> Any
741        tag = None
742        style = ''
743        anchor = data.yaml_anchor(any=True)
744        if PY2 and not isinstance(data, unicode):
745            data = unicode(data, 'ascii')
746        tag = u'tag:yaml.org,2002:str'
747        return self.represent_scalar(tag, data, style=style, anchor=anchor)
748
749    def insert_underscore(self, prefix, s, underscore, anchor=None):
750        # type: (Any, Any, Any, Any) -> Any
751        if underscore is None:
752            return self.represent_scalar(u'tag:yaml.org,2002:int', prefix + s, anchor=anchor)
753        if underscore[0]:
754            sl = list(s)
755            pos = len(s) - underscore[0]
756            while pos > 0:
757                sl.insert(pos, '_')
758                pos -= underscore[0]
759            s = "".join(sl)
760        if underscore[1]:
761            s = '_' + s
762        if underscore[2]:
763            s += '_'
764        return self.represent_scalar(u'tag:yaml.org,2002:int', prefix + s, anchor=anchor)
765
766    def represent_scalar_int(self, data):
767        # type: (Any) -> Any
768        if data._width is not None:
769            s = '{:0{}d}'.format(data, data._width)
770        else:
771            s = format(data, 'd')
772        anchor = data.yaml_anchor(any=True)
773        return self.insert_underscore("", s, data._underscore, anchor=anchor)
774
775    def represent_binary_int(self, data):
776        # type: (Any) -> Any
777        if data._width is not None:
778            # cannot use '{:#0{}b}', that strips the zeros
779            s = '{:0{}b}'.format(data, data._width)
780        else:
781            s = format(data, 'b')
782        anchor = data.yaml_anchor(any=True)
783        return self.insert_underscore('0b', s, data._underscore, anchor=anchor)
784
785    def represent_octal_int(self, data):
786        # type: (Any) -> Any
787        if data._width is not None:
788            # cannot use '{:#0{}o}', that strips the zeros
789            s = '{:0{}o}'.format(data, data._width)
790        else:
791            s = format(data, 'o')
792        anchor = data.yaml_anchor(any=True)
793        return self.insert_underscore('0o', s, data._underscore, anchor=anchor)
794
795    def represent_hex_int(self, data):
796        # type: (Any) -> Any
797        if data._width is not None:
798            # cannot use '{:#0{}x}', that strips the zeros
799            s = '{:0{}x}'.format(data, data._width)
800        else:
801            s = format(data, 'x')
802        anchor = data.yaml_anchor(any=True)
803        return self.insert_underscore('0x', s, data._underscore, anchor=anchor)
804
805    def represent_hex_caps_int(self, data):
806        # type: (Any) -> Any
807        if data._width is not None:
808            # cannot use '{:#0{}X}', that strips the zeros
809            s = '{:0{}X}'.format(data, data._width)
810        else:
811            s = format(data, 'X')
812        anchor = data.yaml_anchor(any=True)
813        return self.insert_underscore('0x', s, data._underscore, anchor=anchor)
814
815    def represent_scalar_float(self, data):
816        # type: (Any) -> Any
817        """ this is way more complicated """
818        value = None
819        anchor = data.yaml_anchor(any=True)
820        if data != data or (data == 0.0 and data == 1.0):
821            value = u'.nan'
822        elif data == self.inf_value:
823            value = u'.inf'
824        elif data == -self.inf_value:
825            value = u'-.inf'
826        if value:
827            return self.represent_scalar(u'tag:yaml.org,2002:float', value, anchor=anchor)
828        if data._exp is None and data._prec > 0 and data._prec == data._width - 1:
829            # no exponent, but trailing dot
830            value = u'{}{:d}.'.format(data._m_sign if data._m_sign else "", abs(int(data)))
831        elif data._exp is None:
832            # no exponent, "normal" dot
833            prec = data._prec
834            ms = data._m_sign if data._m_sign else ""
835            # -1 for the dot
836            value = u'{}{:0{}.{}f}'.format(
837                ms, abs(data), data._width - len(ms), data._width - prec - 1
838            )
839            if prec == 0 or (prec == 1 and ms != ""):
840                value = value.replace(u'0.', u'.')
841            while len(value) < data._width:
842                value += u'0'
843        else:
844            # exponent
845            m, es = u'{:{}.{}e}'.format(
846                # data, data._width, data._width - data._prec + (1 if data._m_sign else 0)
847                data,
848                data._width,
849                data._width + (1 if data._m_sign else 0),
850            ).split('e')
851            w = data._width if data._prec > 0 else (data._width + 1)
852            if data < 0:
853                w += 1
854            m = m[:w]
855            e = int(es)
856            m1, m2 = m.split('.')  # always second?
857            while len(m1) + len(m2) < data._width - (1 if data._prec >= 0 else 0):
858                m2 += u'0'
859            if data._m_sign and data > 0:
860                m1 = '+' + m1
861            esgn = u'+' if data._e_sign else ""
862            if data._prec < 0:  # mantissa without dot
863                if m2 != u'0':
864                    e -= len(m2)
865                else:
866                    m2 = ""
867                while (len(m1) + len(m2) - (1 if data._m_sign else 0)) < data._width:
868                    m2 += u'0'
869                    e -= 1
870                value = m1 + m2 + data._exp + u'{:{}0{}d}'.format(e, esgn, data._e_width)
871            elif data._prec == 0:  # mantissa with trailing dot
872                e -= len(m2)
873                value = (
874                    m1 + m2 + u'.' + data._exp + u'{:{}0{}d}'.format(e, esgn, data._e_width)
875                )
876            else:
877                if data._m_lead0 > 0:
878                    m2 = u'0' * (data._m_lead0 - 1) + m1 + m2
879                    m1 = u'0'
880                    m2 = m2[: -data._m_lead0]  # these should be zeros
881                    e += data._m_lead0
882                while len(m1) < data._prec:
883                    m1 += m2[0]
884                    m2 = m2[1:]
885                    e -= 1
886                value = (
887                    m1 + u'.' + m2 + data._exp + u'{:{}0{}d}'.format(e, esgn, data._e_width)
888                )
889
890        if value is None:
891            value = to_unicode(repr(data)).lower()
892        return self.represent_scalar(u'tag:yaml.org,2002:float', value, anchor=anchor)
893
894    def represent_sequence(self, tag, sequence, flow_style=None):
895        # type: (Any, Any, Any) -> Any
896        value = []  # type: List[Any]
897        # if the flow_style is None, the flow style tacked on to the object
898        # explicitly will be taken. If that is None as well the default flow
899        # style rules
900        try:
901            flow_style = sequence.fa.flow_style(flow_style)
902        except AttributeError:
903            flow_style = flow_style
904        try:
905            anchor = sequence.yaml_anchor()
906        except AttributeError:
907            anchor = None
908        node = SequenceNode(tag, value, flow_style=flow_style, anchor=anchor)
909        if self.alias_key is not None:
910            self.represented_objects[self.alias_key] = node
911        best_style = True
912        try:
913            comment = getattr(sequence, comment_attrib)
914            node.comment = comment.comment
915            # reset any comment already printed information
916            if node.comment and node.comment[1]:
917                for ct in node.comment[1]:
918                    ct.reset()
919            item_comments = comment.items
920            for v in item_comments.values():
921                if v and v[1]:
922                    for ct in v[1]:
923                        ct.reset()
924            item_comments = comment.items
925            node.comment = comment.comment
926            try:
927                node.comment.append(comment.end)
928            except AttributeError:
929                pass
930        except AttributeError:
931            item_comments = {}
932        for idx, item in enumerate(sequence):
933            node_item = self.represent_data(item)
934            self.merge_comments(node_item, item_comments.get(idx))
935            if not (isinstance(node_item, ScalarNode) and not node_item.style):
936                best_style = False
937            value.append(node_item)
938        if flow_style is None:
939            if len(sequence) != 0 and self.default_flow_style is not None:
940                node.flow_style = self.default_flow_style
941            else:
942                node.flow_style = best_style
943        return node
944
945    def merge_comments(self, node, comments):
946        # type: (Any, Any) -> Any
947        if comments is None:
948            assert hasattr(node, 'comment')
949            return node
950        if getattr(node, 'comment', None) is not None:
951            for idx, val in enumerate(comments):
952                if idx >= len(node.comment):
953                    continue
954                nc = node.comment[idx]
955                if nc is not None:
956                    assert val is None or val == nc
957                    comments[idx] = nc
958        node.comment = comments
959        return node
960
961    def represent_key(self, data):
962        # type: (Any) -> Any
963        if isinstance(data, CommentedKeySeq):
964            self.alias_key = None
965            return self.represent_sequence(u'tag:yaml.org,2002:seq', data, flow_style=True)
966        if isinstance(data, CommentedKeyMap):
967            self.alias_key = None
968            return self.represent_mapping(u'tag:yaml.org,2002:map', data, flow_style=True)
969        return SafeRepresenter.represent_key(self, data)
970
971    def represent_mapping(self, tag, mapping, flow_style=None):
972        # type: (Any, Any, Any) -> Any
973        value = []  # type: List[Any]
974        try:
975            flow_style = mapping.fa.flow_style(flow_style)
976        except AttributeError:
977            flow_style = flow_style
978        try:
979            anchor = mapping.yaml_anchor()
980        except AttributeError:
981            anchor = None
982        node = MappingNode(tag, value, flow_style=flow_style, anchor=anchor)
983        if self.alias_key is not None:
984            self.represented_objects[self.alias_key] = node
985        best_style = True
986        # no sorting! !!
987        try:
988            comment = getattr(mapping, comment_attrib)
989            node.comment = comment.comment
990            if node.comment and node.comment[1]:
991                for ct in node.comment[1]:
992                    ct.reset()
993            item_comments = comment.items
994            for v in item_comments.values():
995                if v and v[1]:
996                    for ct in v[1]:
997                        ct.reset()
998            try:
999                node.comment.append(comment.end)
1000            except AttributeError:
1001                pass
1002        except AttributeError:
1003            item_comments = {}
1004        merge_list = [m[1] for m in getattr(mapping, merge_attrib, [])]
1005        try:
1006            merge_pos = getattr(mapping, merge_attrib, [[0]])[0][0]
1007        except IndexError:
1008            merge_pos = 0
1009        item_count = 0
1010        if bool(merge_list):
1011            items = mapping.non_merged_items()
1012        else:
1013            items = mapping.items()
1014        for item_key, item_value in items:
1015            item_count += 1
1016            node_key = self.represent_key(item_key)
1017            node_value = self.represent_data(item_value)
1018            item_comment = item_comments.get(item_key)
1019            if item_comment:
1020                assert getattr(node_key, 'comment', None) is None
1021                node_key.comment = item_comment[:2]
1022                nvc = getattr(node_value, 'comment', None)
1023                if nvc is not None:  # end comment already there
1024                    nvc[0] = item_comment[2]
1025                    nvc[1] = item_comment[3]
1026                else:
1027                    node_value.comment = item_comment[2:]
1028            if not (isinstance(node_key, ScalarNode) and not node_key.style):
1029                best_style = False
1030            if not (isinstance(node_value, ScalarNode) and not node_value.style):
1031                best_style = False
1032            value.append((node_key, node_value))
1033        if flow_style is None:
1034            if ((item_count != 0) or bool(merge_list)) and self.default_flow_style is not None:
1035                node.flow_style = self.default_flow_style
1036            else:
1037                node.flow_style = best_style
1038        if bool(merge_list):
1039            # because of the call to represent_data here, the anchors
1040            # are marked as being used and thereby created
1041            if len(merge_list) == 1:
1042                arg = self.represent_data(merge_list[0])
1043            else:
1044                arg = self.represent_data(merge_list)
1045                arg.flow_style = True
1046            value.insert(merge_pos, (ScalarNode(u'tag:yaml.org,2002:merge', '<<'), arg))
1047        return node
1048
1049    def represent_omap(self, tag, omap, flow_style=None):
1050        # type: (Any, Any, Any) -> Any
1051        value = []  # type: List[Any]
1052        try:
1053            flow_style = omap.fa.flow_style(flow_style)
1054        except AttributeError:
1055            flow_style = flow_style
1056        try:
1057            anchor = omap.yaml_anchor()
1058        except AttributeError:
1059            anchor = None
1060        node = SequenceNode(tag, value, flow_style=flow_style, anchor=anchor)
1061        if self.alias_key is not None:
1062            self.represented_objects[self.alias_key] = node
1063        best_style = True
1064        try:
1065            comment = getattr(omap, comment_attrib)
1066            node.comment = comment.comment
1067            if node.comment and node.comment[1]:
1068                for ct in node.comment[1]:
1069                    ct.reset()
1070            item_comments = comment.items
1071            for v in item_comments.values():
1072                if v and v[1]:
1073                    for ct in v[1]:
1074                        ct.reset()
1075            try:
1076                node.comment.append(comment.end)
1077            except AttributeError:
1078                pass
1079        except AttributeError:
1080            item_comments = {}
1081        for item_key in omap:
1082            item_val = omap[item_key]
1083            node_item = self.represent_data({item_key: item_val})
1084            # node_item.flow_style = False
1085            # node item has two scalars in value: node_key and node_value
1086            item_comment = item_comments.get(item_key)
1087            if item_comment:
1088                if item_comment[1]:
1089                    node_item.comment = [None, item_comment[1]]
1090                assert getattr(node_item.value[0][0], 'comment', None) is None
1091                node_item.value[0][0].comment = [item_comment[0], None]
1092                nvc = getattr(node_item.value[0][1], 'comment', None)
1093                if nvc is not None:  # end comment already there
1094                    nvc[0] = item_comment[2]
1095                    nvc[1] = item_comment[3]
1096                else:
1097                    node_item.value[0][1].comment = item_comment[2:]
1098            # if not (isinstance(node_item, ScalarNode) \
1099            #    and not node_item.style):
1100            #     best_style = False
1101            value.append(node_item)
1102        if flow_style is None:
1103            if self.default_flow_style is not None:
1104                node.flow_style = self.default_flow_style
1105            else:
1106                node.flow_style = best_style
1107        return node
1108
1109    def represent_set(self, setting):
1110        # type: (Any) -> Any
1111        flow_style = False
1112        tag = u'tag:yaml.org,2002:set'
1113        # return self.represent_mapping(tag, value)
1114        value = []  # type: List[Any]
1115        flow_style = setting.fa.flow_style(flow_style)
1116        try:
1117            anchor = setting.yaml_anchor()
1118        except AttributeError:
1119            anchor = None
1120        node = MappingNode(tag, value, flow_style=flow_style, anchor=anchor)
1121        if self.alias_key is not None:
1122            self.represented_objects[self.alias_key] = node
1123        best_style = True
1124        # no sorting! !!
1125        try:
1126            comment = getattr(setting, comment_attrib)
1127            node.comment = comment.comment
1128            if node.comment and node.comment[1]:
1129                for ct in node.comment[1]:
1130                    ct.reset()
1131            item_comments = comment.items
1132            for v in item_comments.values():
1133                if v and v[1]:
1134                    for ct in v[1]:
1135                        ct.reset()
1136            try:
1137                node.comment.append(comment.end)
1138            except AttributeError:
1139                pass
1140        except AttributeError:
1141            item_comments = {}
1142        for item_key in setting.odict:
1143            node_key = self.represent_key(item_key)
1144            node_value = self.represent_data(None)
1145            item_comment = item_comments.get(item_key)
1146            if item_comment:
1147                assert getattr(node_key, 'comment', None) is None
1148                node_key.comment = item_comment[:2]
1149            node_key.style = node_value.style = '?'
1150            if not (isinstance(node_key, ScalarNode) and not node_key.style):
1151                best_style = False
1152            if not (isinstance(node_value, ScalarNode) and not node_value.style):
1153                best_style = False
1154            value.append((node_key, node_value))
1155        best_style = best_style
1156        return node
1157
1158    def represent_dict(self, data):
1159        # type: (Any) -> Any
1160        """write out tag if saved on loading"""
1161        try:
1162            t = data.tag.value
1163        except AttributeError:
1164            t = None
1165        if t:
1166            if t.startswith('!!'):
1167                tag = 'tag:yaml.org,2002:' + t[2:]
1168            else:
1169                tag = t
1170        else:
1171            tag = u'tag:yaml.org,2002:map'
1172        return self.represent_mapping(tag, data)
1173
1174    def represent_list(self, data):
1175        # type: (Any) -> Any
1176        try:
1177            t = data.tag.value
1178        except AttributeError:
1179            t = None
1180        if t:
1181            if t.startswith('!!'):
1182                tag = 'tag:yaml.org,2002:' + t[2:]
1183            else:
1184                tag = t
1185        else:
1186            tag = u'tag:yaml.org,2002:seq'
1187        return self.represent_sequence(tag, data)
1188
1189    def represent_datetime(self, data):
1190        # type: (Any) -> Any
1191        inter = 'T' if data._yaml['t'] else ' '
1192        _yaml = data._yaml
1193        if _yaml['delta']:
1194            data += _yaml['delta']
1195            value = data.isoformat(inter)
1196        else:
1197            value = data.isoformat(inter)
1198        if _yaml['tz']:
1199            value += _yaml['tz']
1200        return self.represent_scalar(u'tag:yaml.org,2002:timestamp', to_unicode(value))
1201
1202    def represent_tagged_scalar(self, data):
1203        # type: (Any) -> Any
1204        try:
1205            tag = data.tag.value
1206        except AttributeError:
1207            tag = None
1208        try:
1209            anchor = data.yaml_anchor()
1210        except AttributeError:
1211            anchor = None
1212        return self.represent_scalar(tag, data.value, style=data.style, anchor=anchor)
1213
1214    def represent_scalar_bool(self, data):
1215        # type: (Any) -> Any
1216        try:
1217            anchor = data.yaml_anchor()
1218        except AttributeError:
1219            anchor = None
1220        return SafeRepresenter.represent_bool(self, data, anchor=anchor)
1221
1222
1223RoundTripRepresenter.add_representer(type(None), RoundTripRepresenter.represent_none)
1224
1225RoundTripRepresenter.add_representer(
1226    LiteralScalarString, RoundTripRepresenter.represent_literal_scalarstring
1227)
1228
1229RoundTripRepresenter.add_representer(
1230    FoldedScalarString, RoundTripRepresenter.represent_folded_scalarstring
1231)
1232
1233RoundTripRepresenter.add_representer(
1234    SingleQuotedScalarString, RoundTripRepresenter.represent_single_quoted_scalarstring
1235)
1236
1237RoundTripRepresenter.add_representer(
1238    DoubleQuotedScalarString, RoundTripRepresenter.represent_double_quoted_scalarstring
1239)
1240
1241RoundTripRepresenter.add_representer(
1242    PlainScalarString, RoundTripRepresenter.represent_plain_scalarstring
1243)
1244
1245# RoundTripRepresenter.add_representer(tuple, Representer.represent_tuple)
1246
1247RoundTripRepresenter.add_representer(ScalarInt, RoundTripRepresenter.represent_scalar_int)
1248
1249RoundTripRepresenter.add_representer(BinaryInt, RoundTripRepresenter.represent_binary_int)
1250
1251RoundTripRepresenter.add_representer(OctalInt, RoundTripRepresenter.represent_octal_int)
1252
1253RoundTripRepresenter.add_representer(HexInt, RoundTripRepresenter.represent_hex_int)
1254
1255RoundTripRepresenter.add_representer(HexCapsInt, RoundTripRepresenter.represent_hex_caps_int)
1256
1257RoundTripRepresenter.add_representer(ScalarFloat, RoundTripRepresenter.represent_scalar_float)
1258
1259RoundTripRepresenter.add_representer(ScalarBoolean, RoundTripRepresenter.represent_scalar_bool)
1260
1261RoundTripRepresenter.add_representer(CommentedSeq, RoundTripRepresenter.represent_list)
1262
1263RoundTripRepresenter.add_representer(CommentedMap, RoundTripRepresenter.represent_dict)
1264
1265RoundTripRepresenter.add_representer(
1266    CommentedOrderedMap, RoundTripRepresenter.represent_ordereddict
1267)
1268
1269if sys.version_info >= (2, 7):
1270    import collections
1271
1272    RoundTripRepresenter.add_representer(
1273        collections.OrderedDict, RoundTripRepresenter.represent_ordereddict
1274    )
1275
1276RoundTripRepresenter.add_representer(CommentedSet, RoundTripRepresenter.represent_set)
1277
1278RoundTripRepresenter.add_representer(
1279    TaggedScalar, RoundTripRepresenter.represent_tagged_scalar
1280)
1281
1282RoundTripRepresenter.add_representer(TimeStamp, RoundTripRepresenter.represent_datetime)
1283