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