resolver.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389
  1. # coding: utf-8
  2. import re
  3. from typing import Any, Dict, List, Union, Text, Optional # NOQA
  4. from ruamel.yaml.compat import VersionType # NOQA
  5. from ruamel.yaml.tag import Tag
  6. from ruamel.yaml.compat import _DEFAULT_YAML_VERSION # NOQA
  7. from ruamel.yaml.error import * # NOQA
  8. from ruamel.yaml.nodes import MappingNode, ScalarNode, SequenceNode # NOQA
  9. from ruamel.yaml.util import RegExp # NOQA
  10. __all__ = ['BaseResolver', 'Resolver', 'VersionedResolver']
  11. # fmt: off
  12. # resolvers consist of
  13. # - a list of applicable version
  14. # - a tag
  15. # - a regexp
  16. # - a list of first characters to match
  17. implicit_resolvers = [
  18. ([(1, 2)],
  19. 'tag:yaml.org,2002:bool',
  20. RegExp('''^(?:true|True|TRUE|false|False|FALSE)$''', re.X),
  21. list('tTfF')),
  22. ([(1, 1)],
  23. 'tag:yaml.org,2002:bool',
  24. RegExp('''^(?:y|Y|yes|Yes|YES|n|N|no|No|NO
  25. |true|True|TRUE|false|False|FALSE
  26. |on|On|ON|off|Off|OFF)$''', re.X),
  27. list('yYnNtTfFoO')),
  28. ([(1, 2)],
  29. 'tag:yaml.org,2002:float',
  30. RegExp('''^(?:
  31. [-+]?(?:[0-9][0-9_]*)\\.[0-9_]*(?:[eE][-+]?[0-9]+)?
  32. |[-+]?(?:[0-9][0-9_]*)(?:[eE][-+]?[0-9]+)
  33. |[-+]?\\.[0-9_]+(?:[eE][-+][0-9]+)?
  34. |[-+]?\\.(?:inf|Inf|INF)
  35. |\\.(?:nan|NaN|NAN))$''', re.X),
  36. list('-+0123456789.')),
  37. ([(1, 1)],
  38. 'tag:yaml.org,2002:float',
  39. RegExp('''^(?:
  40. [-+]?(?:[0-9][0-9_]*)\\.[0-9_]*(?:[eE][-+]?[0-9]+)?
  41. |[-+]?(?:[0-9][0-9_]*)(?:[eE][-+]?[0-9]+)
  42. |\\.[0-9_]+(?:[eE][-+][0-9]+)?
  43. |[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+\\.[0-9_]* # sexagesimal float
  44. |[-+]?\\.(?:inf|Inf|INF)
  45. |\\.(?:nan|NaN|NAN))$''', re.X),
  46. list('-+0123456789.')),
  47. ([(1, 2)],
  48. 'tag:yaml.org,2002:int',
  49. RegExp('''^(?:[-+]?0b[0-1_]+
  50. |[-+]?0o?[0-7_]+
  51. |[-+]?[0-9_]+
  52. |[-+]?0x[0-9a-fA-F_]+)$''', re.X),
  53. list('-+0123456789')),
  54. ([(1, 1)],
  55. 'tag:yaml.org,2002:int',
  56. RegExp('''^(?:[-+]?0b[0-1_]+
  57. |[-+]?0?[0-7_]+
  58. |[-+]?(?:0|[1-9][0-9_]*)
  59. |[-+]?0x[0-9a-fA-F_]+
  60. |[-+]?[1-9][0-9_]*(?::[0-5]?[0-9])+)$''', re.X), # sexagesimal int
  61. list('-+0123456789')),
  62. ([(1, 2), (1, 1)],
  63. 'tag:yaml.org,2002:merge',
  64. RegExp('^(?:<<)$'),
  65. ['<']),
  66. ([(1, 2), (1, 1)],
  67. 'tag:yaml.org,2002:null',
  68. RegExp('''^(?: ~
  69. |null|Null|NULL
  70. | )$''', re.X),
  71. ['~', 'n', 'N', '']),
  72. ([(1, 2), (1, 1)],
  73. 'tag:yaml.org,2002:timestamp',
  74. RegExp('''^(?:[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]
  75. |[0-9][0-9][0-9][0-9] -[0-9][0-9]? -[0-9][0-9]?
  76. (?:[Tt]|[ \\t]+)[0-9][0-9]?
  77. :[0-9][0-9] :[0-9][0-9] (?:\\.[0-9]*)?
  78. (?:[ \\t]*(?:Z|[-+][0-9][0-9]?(?::[0-9][0-9])?))?)$''', re.X),
  79. list('0123456789')),
  80. ([(1, 2), (1, 1)],
  81. 'tag:yaml.org,2002:value',
  82. RegExp('^(?:=)$'),
  83. ['=']),
  84. # The following resolver is only for documentation purposes. It cannot work
  85. # because plain scalars cannot start with '!', '&', or '*'.
  86. ([(1, 2), (1, 1)],
  87. 'tag:yaml.org,2002:yaml',
  88. RegExp('^(?:!|&|\\*)$'),
  89. list('!&*')),
  90. ]
  91. # fmt: on
  92. class ResolverError(YAMLError):
  93. pass
  94. class BaseResolver:
  95. DEFAULT_SCALAR_TAG = Tag(suffix='tag:yaml.org,2002:str')
  96. DEFAULT_SEQUENCE_TAG = Tag(suffix='tag:yaml.org,2002:seq')
  97. DEFAULT_MAPPING_TAG = Tag(suffix='tag:yaml.org,2002:map')
  98. yaml_implicit_resolvers: Dict[Any, Any] = {}
  99. yaml_path_resolvers: Dict[Any, Any] = {}
  100. def __init__(self: Any, loadumper: Any = None) -> None:
  101. self.loadumper = loadumper
  102. if self.loadumper is not None and getattr(self.loadumper, '_resolver', None) is None:
  103. self.loadumper._resolver = self.loadumper
  104. self._loader_version: Any = None
  105. self.resolver_exact_paths: List[Any] = []
  106. self.resolver_prefix_paths: List[Any] = []
  107. @property
  108. def parser(self) -> Any:
  109. if self.loadumper is not None:
  110. if hasattr(self.loadumper, 'typ'):
  111. return self.loadumper.parser
  112. return self.loadumper._parser
  113. return None
  114. @classmethod
  115. def add_implicit_resolver_base(cls, tag: Any, regexp: Any, first: Any) -> None:
  116. if 'yaml_implicit_resolvers' not in cls.__dict__:
  117. # deepcopy doesn't work here
  118. cls.yaml_implicit_resolvers = {
  119. k: cls.yaml_implicit_resolvers[k][:] for k in cls.yaml_implicit_resolvers
  120. }
  121. if first is None:
  122. first = [None]
  123. for ch in first:
  124. cls.yaml_implicit_resolvers.setdefault(ch, []).append((tag, regexp))
  125. @classmethod
  126. def add_implicit_resolver(cls, tag: Any, regexp: Any, first: Any) -> None:
  127. if 'yaml_implicit_resolvers' not in cls.__dict__:
  128. # deepcopy doesn't work here
  129. cls.yaml_implicit_resolvers = {
  130. k: cls.yaml_implicit_resolvers[k][:] for k in cls.yaml_implicit_resolvers
  131. }
  132. if first is None:
  133. first = [None]
  134. for ch in first:
  135. cls.yaml_implicit_resolvers.setdefault(ch, []).append((tag, regexp))
  136. implicit_resolvers.append(([(1, 2), (1, 1)], tag, regexp, first))
  137. # @classmethod
  138. # def add_implicit_resolver(cls, tag, regexp, first):
  139. @classmethod
  140. def add_path_resolver(cls, tag: Any, path: Any, kind: Any = None) -> None:
  141. # Note: `add_path_resolver` is experimental. The API could be changed.
  142. # `new_path` is a pattern that is matched against the path from the
  143. # root to the node that is being considered. `node_path` elements are
  144. # tuples `(node_check, index_check)`. `node_check` is a node class:
  145. # `ScalarNode`, `SequenceNode`, `MappingNode` or `None`. `None`
  146. # matches any kind of a node. `index_check` could be `None`, a boolean
  147. # value, a string value, or a number. `None` and `False` match against
  148. # any _value_ of sequence and mapping nodes. `True` matches against
  149. # any _key_ of a mapping node. A string `index_check` matches against
  150. # a mapping value that corresponds to a scalar key which content is
  151. # equal to the `index_check` value. An integer `index_check` matches
  152. # against a sequence value with the index equal to `index_check`.
  153. if 'yaml_path_resolvers' not in cls.__dict__:
  154. cls.yaml_path_resolvers = cls.yaml_path_resolvers.copy()
  155. new_path: List[Any] = []
  156. for element in path:
  157. if isinstance(element, (list, tuple)):
  158. if len(element) == 2:
  159. node_check, index_check = element
  160. elif len(element) == 1:
  161. node_check = element[0]
  162. index_check = True
  163. else:
  164. raise ResolverError(f'Invalid path element: {element!s}')
  165. else:
  166. node_check = None
  167. index_check = element
  168. if node_check is str:
  169. node_check = ScalarNode
  170. elif node_check is list:
  171. node_check = SequenceNode
  172. elif node_check is dict:
  173. node_check = MappingNode
  174. elif (
  175. node_check not in [ScalarNode, SequenceNode, MappingNode]
  176. and not isinstance(node_check, str)
  177. and node_check is not None
  178. ):
  179. raise ResolverError(f'Invalid node checker: {node_check!s}')
  180. if not isinstance(index_check, (str, int)) and index_check is not None:
  181. raise ResolverError(f'Invalid index checker: {index_check!s}')
  182. new_path.append((node_check, index_check))
  183. if kind is str:
  184. kind = ScalarNode
  185. elif kind is list:
  186. kind = SequenceNode
  187. elif kind is dict:
  188. kind = MappingNode
  189. elif kind not in [ScalarNode, SequenceNode, MappingNode] and kind is not None:
  190. raise ResolverError(f'Invalid node kind: {kind!s}')
  191. cls.yaml_path_resolvers[tuple(new_path), kind] = tag
  192. def descend_resolver(self, current_node: Any, current_index: Any) -> None:
  193. if not self.yaml_path_resolvers:
  194. return
  195. exact_paths = {}
  196. prefix_paths = []
  197. if current_node:
  198. depth = len(self.resolver_prefix_paths)
  199. for path, kind in self.resolver_prefix_paths[-1]:
  200. if self.check_resolver_prefix(depth, path, kind, current_node, current_index):
  201. if len(path) > depth:
  202. prefix_paths.append((path, kind))
  203. else:
  204. exact_paths[kind] = self.yaml_path_resolvers[path, kind]
  205. else:
  206. for path, kind in self.yaml_path_resolvers:
  207. if not path:
  208. exact_paths[kind] = self.yaml_path_resolvers[path, kind]
  209. else:
  210. prefix_paths.append((path, kind))
  211. self.resolver_exact_paths.append(exact_paths)
  212. self.resolver_prefix_paths.append(prefix_paths)
  213. def ascend_resolver(self) -> None:
  214. if not self.yaml_path_resolvers:
  215. return
  216. self.resolver_exact_paths.pop()
  217. self.resolver_prefix_paths.pop()
  218. def check_resolver_prefix(
  219. self, depth: int, path: Any, kind: Any, current_node: Any, current_index: Any,
  220. ) -> bool:
  221. node_check, index_check = path[depth - 1]
  222. if isinstance(node_check, str):
  223. if current_node.tag != node_check:
  224. return False
  225. elif node_check is not None:
  226. if not isinstance(current_node, node_check):
  227. return False
  228. if index_check is True and current_index is not None:
  229. return False
  230. if (index_check is False or index_check is None) and current_index is None:
  231. return False
  232. if isinstance(index_check, str):
  233. if not (
  234. isinstance(current_index, ScalarNode) and index_check == current_index.value
  235. ):
  236. return False
  237. elif isinstance(index_check, int) and not isinstance(index_check, bool):
  238. if index_check != current_index:
  239. return False
  240. return True
  241. def resolve(self, kind: Any, value: Any, implicit: Any) -> Any:
  242. if kind is ScalarNode and implicit[0]:
  243. if value == "":
  244. resolvers = self.yaml_implicit_resolvers.get("", [])
  245. else:
  246. resolvers = self.yaml_implicit_resolvers.get(value[0], [])
  247. resolvers += self.yaml_implicit_resolvers.get(None, [])
  248. for tag, regexp in resolvers:
  249. if regexp.match(value):
  250. return Tag(suffix=tag)
  251. implicit = implicit[1]
  252. if bool(self.yaml_path_resolvers):
  253. exact_paths = self.resolver_exact_paths[-1]
  254. if kind in exact_paths:
  255. return Tag(suffix=exact_paths[kind])
  256. if None in exact_paths:
  257. return Tag(suffix=exact_paths[None])
  258. if kind is ScalarNode:
  259. return self.DEFAULT_SCALAR_TAG
  260. elif kind is SequenceNode:
  261. return self.DEFAULT_SEQUENCE_TAG
  262. elif kind is MappingNode:
  263. return self.DEFAULT_MAPPING_TAG
  264. @property
  265. def processing_version(self) -> Any:
  266. return None
  267. class Resolver(BaseResolver):
  268. pass
  269. for ir in implicit_resolvers:
  270. if (1, 2) in ir[0]:
  271. Resolver.add_implicit_resolver_base(*ir[1:])
  272. class VersionedResolver(BaseResolver):
  273. """
  274. contrary to the "normal" resolver, the smart resolver delays loading
  275. the pattern matching rules. That way it can decide to load 1.1 rules
  276. or the (default) 1.2 rules, that no longer support octal without 0o, sexagesimals
  277. and Yes/No/On/Off booleans.
  278. """
  279. def __init__(
  280. self, version: Optional[VersionType] = None, loader: Any = None, loadumper: Any = None,
  281. ) -> None:
  282. if loader is None and loadumper is not None:
  283. loader = loadumper
  284. BaseResolver.__init__(self, loader)
  285. self._loader_version = self.get_loader_version(version)
  286. self._version_implicit_resolver: Dict[Any, Any] = {}
  287. def add_version_implicit_resolver(
  288. self, version: VersionType, tag: Any, regexp: Any, first: Any,
  289. ) -> None:
  290. if first is None:
  291. first = [None]
  292. impl_resolver = self._version_implicit_resolver.setdefault(version, {})
  293. for ch in first:
  294. impl_resolver.setdefault(ch, []).append((tag, regexp))
  295. def get_loader_version(self, version: Optional[VersionType]) -> Any:
  296. if version is None or isinstance(version, tuple):
  297. return version
  298. if isinstance(version, list):
  299. return tuple(version)
  300. # assume string
  301. return tuple(map(int, version.split('.')))
  302. @property
  303. def versioned_resolver(self) -> Any:
  304. """
  305. select the resolver based on the version we are parsing
  306. """
  307. version = self.processing_version
  308. if isinstance(version, str):
  309. version = tuple(map(int, version.split('.')))
  310. if version not in self._version_implicit_resolver:
  311. for x in implicit_resolvers:
  312. if version in x[0]:
  313. self.add_version_implicit_resolver(version, x[1], x[2], x[3])
  314. return self._version_implicit_resolver[version]
  315. def resolve(self, kind: Any, value: Any, implicit: Any) -> Any:
  316. if kind is ScalarNode and implicit[0]:
  317. if value == "":
  318. resolvers = self.versioned_resolver.get("", [])
  319. else:
  320. resolvers = self.versioned_resolver.get(value[0], [])
  321. resolvers += self.versioned_resolver.get(None, [])
  322. for tag, regexp in resolvers:
  323. if regexp.match(value):
  324. return Tag(suffix=tag)
  325. implicit = implicit[1]
  326. if bool(self.yaml_path_resolvers):
  327. exact_paths = self.resolver_exact_paths[-1]
  328. if kind in exact_paths:
  329. return Tag(suffix=exact_paths[kind])
  330. if None in exact_paths:
  331. return Tag(suffix=exact_paths[None])
  332. if kind is ScalarNode:
  333. return self.DEFAULT_SCALAR_TAG
  334. elif kind is SequenceNode:
  335. return self.DEFAULT_SEQUENCE_TAG
  336. elif kind is MappingNode:
  337. return self.DEFAULT_MAPPING_TAG
  338. @property
  339. def processing_version(self) -> Any:
  340. try:
  341. version = self.loadumper._scanner.yaml_version
  342. except AttributeError:
  343. try:
  344. if hasattr(self.loadumper, 'typ'):
  345. version = self.loadumper.version
  346. else:
  347. version = self.loadumper._serializer.use_version # dumping
  348. except AttributeError:
  349. version = None
  350. if version is None:
  351. version = self._loader_version
  352. if version is None:
  353. version = _DEFAULT_YAML_VERSION
  354. return version