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.
 
 
 
 

232 lines
8.0 KiB

  1. from __future__ import annotations
  2. import warnings
  3. from ruamel.yaml.error import MarkedYAMLError, ReusedAnchorWarning
  4. from ruamel.yaml.compat import nprint, nprintf # NOQA
  5. from ruamel.yaml.events import (
  6. StreamStartEvent,
  7. StreamEndEvent,
  8. MappingStartEvent,
  9. MappingEndEvent,
  10. SequenceStartEvent,
  11. SequenceEndEvent,
  12. AliasEvent,
  13. ScalarEvent,
  14. )
  15. from ruamel.yaml.nodes import MappingNode, ScalarNode, SequenceNode
  16. if False: # MYPY
  17. from typing import Any, Dict, Optional, List # NOQA
  18. __all__ = ['Composer', 'ComposerError']
  19. class ComposerError(MarkedYAMLError):
  20. pass
  21. class Composer:
  22. def __init__(self, loader: Any = None) -> None:
  23. self.loader = loader
  24. if self.loader is not None and getattr(self.loader, '_composer', None) is None:
  25. self.loader._composer = self
  26. self.anchors: Dict[Any, Any] = {}
  27. self.warn_double_anchors = True
  28. @property
  29. def parser(self) -> Any:
  30. if hasattr(self.loader, 'typ'):
  31. self.loader.parser
  32. return self.loader._parser
  33. @property
  34. def resolver(self) -> Any:
  35. # assert self.loader._resolver is not None
  36. if hasattr(self.loader, 'typ'):
  37. self.loader.resolver
  38. return self.loader._resolver
  39. def check_node(self) -> Any:
  40. # Drop the STREAM-START event.
  41. if self.parser.check_event(StreamStartEvent):
  42. self.parser.get_event()
  43. # If there are more documents available?
  44. return not self.parser.check_event(StreamEndEvent)
  45. def get_node(self) -> Any:
  46. # Get the root node of the next document.
  47. if not self.parser.check_event(StreamEndEvent):
  48. return self.compose_document()
  49. def get_single_node(self) -> Any:
  50. # Drop the STREAM-START event.
  51. self.parser.get_event()
  52. # Compose a document if the stream is not empty.
  53. document: Any = None
  54. if not self.parser.check_event(StreamEndEvent):
  55. document = self.compose_document()
  56. # Ensure that the stream contains no more documents.
  57. if not self.parser.check_event(StreamEndEvent):
  58. event = self.parser.get_event()
  59. raise ComposerError(
  60. 'expected a single document in the stream',
  61. document.start_mark,
  62. 'but found another document',
  63. event.start_mark,
  64. )
  65. # Drop the STREAM-END event.
  66. self.parser.get_event()
  67. return document
  68. def compose_document(self: Any) -> Any:
  69. self.anchors = {}
  70. # Drop the DOCUMENT-START event.
  71. self.parser.get_event()
  72. # Compose the root node.
  73. node = self.compose_node(None, None)
  74. # Drop the DOCUMENT-END event.
  75. self.parser.get_event()
  76. return node
  77. def return_alias(self, a: Any) -> Any:
  78. return a
  79. def compose_node(self, parent: Any, index: Any) -> Any:
  80. if self.parser.check_event(AliasEvent):
  81. event = self.parser.get_event()
  82. alias = event.anchor
  83. if alias not in self.anchors:
  84. raise ComposerError(
  85. None, None, f'found undefined alias {alias!r}', event.start_mark,
  86. )
  87. return self.return_alias(self.anchors[alias])
  88. event = self.parser.peek_event()
  89. anchor = event.anchor
  90. if anchor is not None: # have an anchor
  91. if self.warn_double_anchors and anchor in self.anchors:
  92. ws = (
  93. f'\nfound duplicate anchor {anchor!r}\n'
  94. f'first occurrence {self.anchors[anchor].start_mark}\n'
  95. f'second occurrence {event.start_mark}'
  96. )
  97. warnings.warn(ws, ReusedAnchorWarning, stacklevel=2)
  98. self.resolver.descend_resolver(parent, index)
  99. if self.parser.check_event(ScalarEvent):
  100. node = self.compose_scalar_node(anchor)
  101. elif self.parser.check_event(SequenceStartEvent):
  102. node = self.compose_sequence_node(anchor)
  103. elif self.parser.check_event(MappingStartEvent):
  104. node = self.compose_mapping_node(anchor)
  105. self.resolver.ascend_resolver()
  106. return node
  107. def compose_scalar_node(self, anchor: Any) -> Any:
  108. event = self.parser.get_event()
  109. tag = event.ctag
  110. if tag is None or str(tag) == '!':
  111. tag = self.resolver.resolve(ScalarNode, event.value, event.implicit)
  112. assert not isinstance(tag, str)
  113. # e.g tag.yaml.org,2002:str
  114. node = ScalarNode(
  115. tag,
  116. event.value,
  117. event.start_mark,
  118. event.end_mark,
  119. style=event.style,
  120. comment=event.comment,
  121. anchor=anchor,
  122. )
  123. if anchor is not None:
  124. self.anchors[anchor] = node
  125. return node
  126. def compose_sequence_node(self, anchor: Any) -> Any:
  127. start_event = self.parser.get_event()
  128. tag = start_event.ctag
  129. if tag is None or str(tag) == '!':
  130. tag = self.resolver.resolve(SequenceNode, None, start_event.implicit)
  131. assert not isinstance(tag, str)
  132. node = SequenceNode(
  133. tag,
  134. [],
  135. start_event.start_mark,
  136. None,
  137. flow_style=start_event.flow_style,
  138. comment=start_event.comment,
  139. anchor=anchor,
  140. )
  141. if anchor is not None:
  142. self.anchors[anchor] = node
  143. index = 0
  144. while not self.parser.check_event(SequenceEndEvent):
  145. node.value.append(self.compose_node(node, index))
  146. index += 1
  147. end_event = self.parser.get_event()
  148. if node.flow_style is True and end_event.comment is not None:
  149. if node.comment is not None:
  150. x = node.flow_style
  151. nprint(
  152. f'Warning: unexpected end_event commment in sequence node {x}\n',
  153. ' if possible, please report an issue with reproducable data/code',
  154. )
  155. node.comment = end_event.comment
  156. node.end_mark = end_event.end_mark
  157. self.check_end_doc_comment(end_event, node)
  158. return node
  159. def compose_mapping_node(self, anchor: Any) -> Any:
  160. start_event = self.parser.get_event()
  161. tag = start_event.ctag
  162. if tag is None or str(tag) == '!':
  163. tag = self.resolver.resolve(MappingNode, None, start_event.implicit)
  164. assert not isinstance(tag, str)
  165. node = MappingNode(
  166. tag,
  167. [],
  168. start_event.start_mark,
  169. None,
  170. flow_style=start_event.flow_style,
  171. comment=start_event.comment,
  172. anchor=anchor,
  173. )
  174. if anchor is not None:
  175. self.anchors[anchor] = node
  176. while not self.parser.check_event(MappingEndEvent):
  177. # key_event = self.parser.peek_event()
  178. item_key = self.compose_node(node, None)
  179. # if item_key in node.value:
  180. # raise ComposerError("while composing a mapping",
  181. # start_event.start_mark,
  182. # "found duplicate key", key_event.start_mark)
  183. item_value = self.compose_node(node, item_key)
  184. # node.value[item_key] = item_value
  185. node.value.append((item_key, item_value))
  186. end_event = self.parser.get_event()
  187. if node.flow_style is True and end_event.comment is not None:
  188. node.comment = end_event.comment
  189. node.end_mark = end_event.end_mark
  190. self.check_end_doc_comment(end_event, node)
  191. return node
  192. def check_end_doc_comment(self, end_event: Any, node: Any) -> None:
  193. if end_event.comment and end_event.comment[1]:
  194. # pre comments on an end_event, no following to move to
  195. if node.comment is None:
  196. node.comment = [None, None]
  197. assert not isinstance(node, ScalarEvent)
  198. # this is a post comment on a mapping node, add as third element
  199. # in the list
  200. node.comment.append(end_event.comment[1])
  201. end_event.comment[1] = None