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.
 
 
 
 

234 lines
8.2 KiB

  1. from __future__ import annotations
  2. from ruamel.yaml.error import YAMLError
  3. from ruamel.yaml.compat import nprint, DBG_NODE, dbg, nprintf # NOQA
  4. from ruamel.yaml.util import RegExp
  5. from ruamel.yaml.events import (
  6. StreamStartEvent,
  7. StreamEndEvent,
  8. MappingStartEvent,
  9. MappingEndEvent,
  10. SequenceStartEvent,
  11. SequenceEndEvent,
  12. AliasEvent,
  13. ScalarEvent,
  14. DocumentStartEvent,
  15. DocumentEndEvent,
  16. )
  17. from ruamel.yaml.nodes import MappingNode, ScalarNode, SequenceNode
  18. if False: # MYPY
  19. from typing import Any, Dict, Union, Text, Optional # NOQA
  20. from ruamel.yaml.compat import VersionType # NOQA
  21. __all__ = ['Serializer', 'SerializerError']
  22. class SerializerError(YAMLError):
  23. pass
  24. class Serializer:
  25. # 'id' and 3+ numbers, but not 000
  26. ANCHOR_TEMPLATE = 'id{:03d}'
  27. ANCHOR_RE = RegExp('id(?!000$)\\d{3,}')
  28. def __init__(
  29. self,
  30. encoding: Any = None,
  31. explicit_start: Optional[bool] = None,
  32. explicit_end: Optional[bool] = None,
  33. version: Optional[VersionType] = None,
  34. tags: Any = None,
  35. dumper: Any = None,
  36. ) -> None:
  37. # NOQA
  38. self.dumper = dumper
  39. if self.dumper is not None:
  40. self.dumper._serializer = self
  41. self.use_encoding = encoding
  42. self.use_explicit_start = explicit_start
  43. self.use_explicit_end = explicit_end
  44. if isinstance(version, str):
  45. self.use_version = tuple(map(int, version.split('.')))
  46. else:
  47. self.use_version = version # type: ignore
  48. self.use_tags = tags
  49. self.serialized_nodes: Dict[Any, Any] = {}
  50. self.anchors: Dict[Any, Any] = {}
  51. self.last_anchor_id = 0
  52. self.closed: Optional[bool] = None
  53. self._templated_id = None
  54. @property
  55. def emitter(self) -> Any:
  56. if hasattr(self.dumper, 'typ'):
  57. return self.dumper.emitter
  58. return self.dumper._emitter
  59. @property
  60. def resolver(self) -> Any:
  61. if hasattr(self.dumper, 'typ'):
  62. self.dumper.resolver
  63. return self.dumper._resolver
  64. def open(self) -> None:
  65. if self.closed is None:
  66. self.emitter.emit(StreamStartEvent(encoding=self.use_encoding))
  67. self.closed = False
  68. elif self.closed:
  69. raise SerializerError('serializer is closed')
  70. else:
  71. raise SerializerError('serializer is already opened')
  72. def close(self) -> None:
  73. if self.closed is None:
  74. raise SerializerError('serializer is not opened')
  75. elif not self.closed:
  76. self.emitter.emit(StreamEndEvent())
  77. self.closed = True
  78. # def __del__(self):
  79. # self.close()
  80. def serialize(self, node: Any) -> None:
  81. if dbg(DBG_NODE):
  82. nprint('Serializing nodes')
  83. node.dump()
  84. if self.closed is None:
  85. raise SerializerError('serializer is not opened')
  86. elif self.closed:
  87. raise SerializerError('serializer is closed')
  88. self.emitter.emit(
  89. DocumentStartEvent(
  90. explicit=self.use_explicit_start, version=self.use_version, tags=self.use_tags,
  91. ),
  92. )
  93. self.anchor_node(node)
  94. self.serialize_node(node, None, None)
  95. self.emitter.emit(DocumentEndEvent(explicit=self.use_explicit_end))
  96. self.serialized_nodes = {}
  97. self.anchors = {}
  98. self.last_anchor_id = 0
  99. def anchor_node(self, node: Any) -> None:
  100. if node in self.anchors:
  101. if self.anchors[node] is None:
  102. self.anchors[node] = self.generate_anchor(node)
  103. else:
  104. anchor = None
  105. try:
  106. if node.anchor.always_dump:
  107. anchor = node.anchor.value
  108. except: # NOQA
  109. pass
  110. self.anchors[node] = anchor
  111. if isinstance(node, SequenceNode):
  112. for item in node.value:
  113. self.anchor_node(item)
  114. elif isinstance(node, MappingNode):
  115. for key, value in node.value:
  116. self.anchor_node(key)
  117. self.anchor_node(value)
  118. def generate_anchor(self, node: Any) -> Any:
  119. try:
  120. anchor = node.anchor.value
  121. except: # NOQA
  122. anchor = None
  123. if anchor is None:
  124. self.last_anchor_id += 1
  125. return self.ANCHOR_TEMPLATE.format(self.last_anchor_id)
  126. return anchor
  127. def serialize_node(self, node: Any, parent: Any, index: Any) -> None:
  128. alias = self.anchors[node]
  129. if node in self.serialized_nodes:
  130. node_style = getattr(node, 'style', None)
  131. if node_style != '?':
  132. node_style = None
  133. self.emitter.emit(AliasEvent(alias, style=node_style))
  134. else:
  135. self.serialized_nodes[node] = True
  136. self.resolver.descend_resolver(parent, index)
  137. if isinstance(node, ScalarNode):
  138. # here check if the node.tag equals the one that would result from parsing
  139. # if not equal quoting is necessary for strings
  140. detected_tag = self.resolver.resolve(ScalarNode, node.value, (True, False))
  141. default_tag = self.resolver.resolve(ScalarNode, node.value, (False, True))
  142. implicit = (
  143. (node.ctag == detected_tag),
  144. (node.ctag == default_tag),
  145. node.tag.startswith('tag:yaml.org,2002:'), # type: ignore
  146. )
  147. self.emitter.emit(
  148. ScalarEvent(
  149. alias,
  150. node.ctag,
  151. implicit,
  152. node.value,
  153. style=node.style,
  154. comment=node.comment,
  155. ),
  156. )
  157. elif isinstance(node, SequenceNode):
  158. implicit = node.ctag == self.resolver.resolve(SequenceNode, node.value, True)
  159. comment = node.comment
  160. end_comment = None
  161. seq_comment = None
  162. if node.flow_style is True:
  163. if comment: # eol comment on flow style sequence
  164. seq_comment = comment[0]
  165. # comment[0] = None
  166. if comment and len(comment) > 2:
  167. end_comment = comment[2]
  168. else:
  169. end_comment = None
  170. self.emitter.emit(
  171. SequenceStartEvent(
  172. alias,
  173. node.ctag,
  174. implicit,
  175. flow_style=node.flow_style,
  176. comment=node.comment,
  177. ),
  178. )
  179. index = 0
  180. for item in node.value:
  181. self.serialize_node(item, node, index)
  182. index += 1
  183. self.emitter.emit(SequenceEndEvent(comment=[seq_comment, end_comment]))
  184. elif isinstance(node, MappingNode):
  185. implicit = node.ctag == self.resolver.resolve(MappingNode, node.value, True)
  186. comment = node.comment
  187. end_comment = None
  188. map_comment = None
  189. if node.flow_style is True:
  190. if comment: # eol comment on flow style sequence
  191. map_comment = comment[0]
  192. # comment[0] = None
  193. if comment and len(comment) > 2:
  194. end_comment = comment[2]
  195. self.emitter.emit(
  196. MappingStartEvent(
  197. alias,
  198. node.ctag,
  199. implicit,
  200. flow_style=node.flow_style,
  201. comment=node.comment,
  202. nr_items=len(node.value),
  203. ),
  204. )
  205. for key, value in node.value:
  206. self.serialize_node(key, node, None)
  207. self.serialize_node(value, node, key)
  208. self.emitter.emit(MappingEndEvent(comment=[map_comment, end_comment]))
  209. self.resolver.ascend_resolver()
  210. def templated_id(s: Text) -> Any:
  211. return Serializer.ANCHOR_RE.match(s)