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.

245 lines
7.6 KiB

4 years ago
  1. # -*- coding: utf-8 -*-
  2. from __future__ import print_function
  3. import re
  4. _MAXCACHE = 20
  5. class Resolver(object):
  6. _match_cache = {}
  7. def __init__(self, pathattr='name'):
  8. """Resolve :any:`NodeMixin` paths using attribute `pathattr`."""
  9. super(Resolver, self).__init__()
  10. self.pathattr = pathattr
  11. def get(self, node, path):
  12. """
  13. Return instance at `path`.
  14. An example module tree:
  15. >>> from anytree import Node
  16. >>> top = Node("top", parent=None)
  17. >>> sub0 = Node("sub0", parent=top)
  18. >>> sub0sub0 = Node("sub0sub0", parent=sub0)
  19. >>> sub0sub1 = Node("sub0sub1", parent=sub0)
  20. >>> sub1 = Node("sub1", parent=top)
  21. A resolver using the `name` attribute:
  22. >>> r = Resolver('name')
  23. Relative paths:
  24. >>> r.get(top, "sub0/sub0sub0")
  25. Node('/top/sub0/sub0sub0')
  26. >>> r.get(sub1, "..")
  27. Node('/top')
  28. >>> r.get(sub1, "../sub0/sub0sub1")
  29. Node('/top/sub0/sub0sub1')
  30. >>> r.get(sub1, ".")
  31. Node('/top/sub1')
  32. >>> r.get(sub1, "")
  33. Node('/top/sub1')
  34. >>> r.get(top, "sub2")
  35. Traceback (most recent call last):
  36. ...
  37. anytree.resolver.ChildResolverError: Node('/top') has no child sub2. Children are: 'sub0', 'sub1'.
  38. Absolute paths:
  39. >>> r.get(sub0sub0, "/top")
  40. Node('/top')
  41. >>> r.get(sub0sub0, "/top/sub0")
  42. Node('/top/sub0')
  43. >>> r.get(sub0sub0, "/")
  44. Traceback (most recent call last):
  45. ...
  46. anytree.resolver.ResolverError: root node missing. root is '/top'.
  47. >>> r.get(sub0sub0, "/bar")
  48. Traceback (most recent call last):
  49. ...
  50. anytree.resolver.ResolverError: unknown root node '/bar'. root is '/top'.
  51. """
  52. node, parts = self.__start(node, path)
  53. for part in parts:
  54. if part == "..":
  55. node = node.parent
  56. elif part in ("", "."):
  57. pass
  58. else:
  59. node = self.__get(node, part)
  60. return node
  61. def __get(self, node, name):
  62. for child in node.children:
  63. if _getattr(child, self.pathattr) == name:
  64. return child
  65. raise ChildResolverError(node, name, self.pathattr)
  66. def glob(self, node, path):
  67. """
  68. Return instances at `path` supporting wildcards.
  69. Behaves identical to :any:`get`, but accepts wildcards and returns
  70. a list of found nodes.
  71. * `*` matches any characters, except '/'.
  72. * `?` matches a single character, except '/'.
  73. An example module tree:
  74. >>> from anytree import Node
  75. >>> top = Node("top", parent=None)
  76. >>> sub0 = Node("sub0", parent=top)
  77. >>> sub0sub0 = Node("sub0", parent=sub0)
  78. >>> sub0sub1 = Node("sub1", parent=sub0)
  79. >>> sub1 = Node("sub1", parent=top)
  80. >>> sub1sub0 = Node("sub0", parent=sub1)
  81. A resolver using the `name` attribute:
  82. >>> r = Resolver('name')
  83. Relative paths:
  84. >>> r.glob(top, "sub0/sub?")
  85. [Node('/top/sub0/sub0'), Node('/top/sub0/sub1')]
  86. >>> r.glob(sub1, ".././*")
  87. [Node('/top/sub0'), Node('/top/sub1')]
  88. >>> r.glob(top, "*/*")
  89. [Node('/top/sub0/sub0'), Node('/top/sub0/sub1'), Node('/top/sub1/sub0')]
  90. >>> r.glob(top, "*/sub0")
  91. [Node('/top/sub0/sub0'), Node('/top/sub1/sub0')]
  92. >>> r.glob(top, "sub1/sub1")
  93. Traceback (most recent call last):
  94. ...
  95. anytree.resolver.ChildResolverError: Node('/top/sub1') has no child sub1. Children are: 'sub0'.
  96. Non-matching wildcards are no error:
  97. >>> r.glob(top, "bar*")
  98. []
  99. >>> r.glob(top, "sub2")
  100. Traceback (most recent call last):
  101. ...
  102. anytree.resolver.ChildResolverError: Node('/top') has no child sub2. Children are: 'sub0', 'sub1'.
  103. Absolute paths:
  104. >>> r.glob(sub0sub0, "/top/*")
  105. [Node('/top/sub0'), Node('/top/sub1')]
  106. >>> r.glob(sub0sub0, "/")
  107. Traceback (most recent call last):
  108. ...
  109. anytree.resolver.ResolverError: root node missing. root is '/top'.
  110. >>> r.glob(sub0sub0, "/bar")
  111. Traceback (most recent call last):
  112. ...
  113. anytree.resolver.ResolverError: unknown root node '/bar'. root is '/top'.
  114. """
  115. node, parts = self.__start(node, path)
  116. return self.__glob(node, parts)
  117. def __start(self, node, path):
  118. sep = node.separator
  119. parts = path.split(sep)
  120. if path.startswith(sep):
  121. node = node.root
  122. rootpart = _getattr(node, self.pathattr)
  123. parts.pop(0)
  124. if not parts[0]:
  125. msg = "root node missing. root is '%s%s'."
  126. raise ResolverError(node, "", msg % (sep, str(rootpart)))
  127. elif parts[0] != rootpart:
  128. msg = "unknown root node '%s%s'. root is '%s%s'."
  129. raise ResolverError(node, "", msg % (sep, parts[0], sep, str(rootpart)))
  130. parts.pop(0)
  131. return node, parts
  132. def __glob(self, node, parts):
  133. nodes = []
  134. name = parts[0]
  135. remainder = parts[1:]
  136. # handle relative
  137. if name == "..":
  138. nodes += self.__glob(node.parent, remainder)
  139. elif name in ("", "."):
  140. nodes += self.__glob(node, remainder)
  141. else:
  142. matches = self.__find(node, name, remainder)
  143. if not matches and not Resolver.is_wildcard(name):
  144. raise ChildResolverError(node, name, self.pathattr)
  145. nodes += matches
  146. return nodes
  147. def __find(self, node, pat, remainder):
  148. matches = []
  149. for child in node.children:
  150. name = _getattr(child, self.pathattr)
  151. try:
  152. if Resolver.__match(name, pat):
  153. if remainder:
  154. matches += self.__glob(child, remainder)
  155. else:
  156. matches.append(child)
  157. except ResolverError as exc:
  158. if not Resolver.is_wildcard(pat):
  159. raise exc
  160. return matches
  161. @staticmethod
  162. def is_wildcard(path):
  163. """Return `True` is a wildcard."""
  164. return "?" in path or "*" in path
  165. @staticmethod
  166. def __match(name, pat):
  167. try:
  168. re_pat = Resolver._match_cache[pat]
  169. except KeyError:
  170. res = Resolver.__translate(pat)
  171. if len(Resolver._match_cache) >= _MAXCACHE:
  172. Resolver._match_cache.clear()
  173. Resolver._match_cache[pat] = re_pat = re.compile(res)
  174. return re_pat.match(name) is not None
  175. @staticmethod
  176. def __translate(pat):
  177. re_pat = ''
  178. for char in pat:
  179. if char == "*":
  180. re_pat += ".*"
  181. elif char == "?":
  182. re_pat += "."
  183. else:
  184. re_pat += re.escape(char)
  185. return re_pat + r'\Z(?ms)'
  186. class ResolverError(RuntimeError):
  187. def __init__(self, node, child, msg):
  188. """Resolve Error at `node` handling `child`."""
  189. super(ResolverError, self).__init__(msg)
  190. self.node = node
  191. self.child = child
  192. class ChildResolverError(ResolverError):
  193. def __init__(self, node, child, pathattr):
  194. """Child Resolve Error at `node` handling `child`."""
  195. names = [repr(_getattr(c, pathattr)) for c in node.children]
  196. msg = "%r has no child %s. Children are: %s."
  197. msg = msg % (node, child, ", ".join(names))
  198. super(ChildResolverError, self).__init__(node, child, msg)
  199. def _getattr(node, name):
  200. return getattr(node, name, None)