resolver.py 15 KB

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