You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

1133 line
43 KiB

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