123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245 |
- # -*- coding: utf-8 -*-
- from __future__ import print_function
- import re
- _MAXCACHE = 20
- class Resolver(object):
- _match_cache = {}
- def __init__(self, pathattr='name'):
- """Resolve :any:`NodeMixin` paths using attribute `pathattr`."""
- super(Resolver, self).__init__()
- self.pathattr = pathattr
- def get(self, node, path):
- """
- Return instance at `path`.
- An example module tree:
- >>> from anytree import Node
- >>> top = Node("top", parent=None)
- >>> sub0 = Node("sub0", parent=top)
- >>> sub0sub0 = Node("sub0sub0", parent=sub0)
- >>> sub0sub1 = Node("sub0sub1", parent=sub0)
- >>> sub1 = Node("sub1", parent=top)
- A resolver using the `name` attribute:
- >>> r = Resolver('name')
- Relative paths:
- >>> r.get(top, "sub0/sub0sub0")
- Node('/top/sub0/sub0sub0')
- >>> r.get(sub1, "..")
- Node('/top')
- >>> r.get(sub1, "../sub0/sub0sub1")
- Node('/top/sub0/sub0sub1')
- >>> r.get(sub1, ".")
- Node('/top/sub1')
- >>> r.get(sub1, "")
- Node('/top/sub1')
- >>> r.get(top, "sub2")
- Traceback (most recent call last):
- ...
- anytree.resolver.ChildResolverError: Node('/top') has no child sub2. Children are: 'sub0', 'sub1'.
- Absolute paths:
- >>> r.get(sub0sub0, "/top")
- Node('/top')
- >>> r.get(sub0sub0, "/top/sub0")
- Node('/top/sub0')
- >>> r.get(sub0sub0, "/")
- Traceback (most recent call last):
- ...
- anytree.resolver.ResolverError: root node missing. root is '/top'.
- >>> r.get(sub0sub0, "/bar")
- Traceback (most recent call last):
- ...
- anytree.resolver.ResolverError: unknown root node '/bar'. root is '/top'.
- """
- node, parts = self.__start(node, path)
- for part in parts:
- if part == "..":
- node = node.parent
- elif part in ("", "."):
- pass
- else:
- node = self.__get(node, part)
- return node
- def __get(self, node, name):
- for child in node.children:
- if str(_getattr(child, self.pathattr)) == name:
- return child
- raise ChildResolverError(node, name, self.pathattr)
- def glob(self, node, path):
- """
- Return instances at `path` supporting wildcards.
- Behaves identical to :any:`get`, but accepts wildcards and returns
- a list of found nodes.
- * `*` matches any characters, except '/'.
- * `?` matches a single character, except '/'.
- An example module tree:
- >>> from anytree import Node
- >>> top = Node("top", parent=None)
- >>> sub0 = Node("sub0", parent=top)
- >>> sub0sub0 = Node("sub0", parent=sub0)
- >>> sub0sub1 = Node("sub1", parent=sub0)
- >>> sub1 = Node("sub1", parent=top)
- >>> sub1sub0 = Node("sub0", parent=sub1)
- A resolver using the `name` attribute:
- >>> r = Resolver('name')
- Relative paths:
- >>> r.glob(top, "sub0/sub?")
- [Node('/top/sub0/sub0'), Node('/top/sub0/sub1')]
- >>> r.glob(sub1, ".././*")
- [Node('/top/sub0'), Node('/top/sub1')]
- >>> r.glob(top, "*/*")
- [Node('/top/sub0/sub0'), Node('/top/sub0/sub1'), Node('/top/sub1/sub0')]
- >>> r.glob(top, "*/sub0")
- [Node('/top/sub0/sub0'), Node('/top/sub1/sub0')]
- >>> r.glob(top, "sub1/sub1")
- Traceback (most recent call last):
- ...
- anytree.resolver.ChildResolverError: Node('/top/sub1') has no child sub1. Children are: 'sub0'.
- Non-matching wildcards are no error:
- >>> r.glob(top, "bar*")
- []
- >>> r.glob(top, "sub2")
- Traceback (most recent call last):
- ...
- anytree.resolver.ChildResolverError: Node('/top') has no child sub2. Children are: 'sub0', 'sub1'.
- Absolute paths:
- >>> r.glob(sub0sub0, "/top/*")
- [Node('/top/sub0'), Node('/top/sub1')]
- >>> r.glob(sub0sub0, "/")
- Traceback (most recent call last):
- ...
- anytree.resolver.ResolverError: root node missing. root is '/top'.
- >>> r.glob(sub0sub0, "/bar")
- Traceback (most recent call last):
- ...
- anytree.resolver.ResolverError: unknown root node '/bar'. root is '/top'.
- """
- node, parts = self.__start(node, path)
- return self.__glob(node, parts)
- def __start(self, node, path):
- sep = node.separator
- parts = path.split(sep)
- if path.startswith(sep):
- node = node.root
- rootpart = str(_getattr(node, self.pathattr))
- parts.pop(0)
- if not parts[0]:
- msg = "root node missing. root is '%s%s'."
- raise ResolverError(node, "", msg % (sep, str(rootpart)))
- elif parts[0] != rootpart:
- msg = "unknown root node '%s%s'. root is '%s%s'."
- raise ResolverError(node, "", msg % (sep, parts[0], sep, str(rootpart)))
- parts.pop(0)
- return node, parts
- def __glob(self, node, parts):
- nodes = []
- name = parts[0]
- remainder = parts[1:]
- # handle relative
- if name == "..":
- nodes += self.__glob(node.parent, remainder)
- elif name in ("", "."):
- nodes += self.__glob(node, remainder)
- else:
- matches = self.__find(node, name, remainder)
- if not matches and not Resolver.is_wildcard(name):
- raise ChildResolverError(node, name, self.pathattr)
- nodes += matches
- return nodes
- def __find(self, node, pat, remainder):
- matches = []
- for child in node.children:
- name = str(_getattr(child, self.pathattr))
- try:
- if Resolver.__match(name, pat):
- if remainder:
- matches += self.__glob(child, remainder)
- else:
- matches.append(child)
- except ResolverError as exc:
- if not Resolver.is_wildcard(pat):
- raise exc
- return matches
- @staticmethod
- def is_wildcard(path):
- """Return `True` is a wildcard."""
- return "?" in path or "*" in path
- @staticmethod
- def __match(name, pat):
- try:
- re_pat = Resolver._match_cache[pat]
- except KeyError:
- res = Resolver.__translate(pat)
- if len(Resolver._match_cache) >= _MAXCACHE:
- Resolver._match_cache.clear()
- Resolver._match_cache[pat] = re_pat = re.compile(res)
- return re_pat.match(name) is not None
- @staticmethod
- def __translate(pat):
- re_pat = ''
- for char in pat:
- if char == "*":
- re_pat += ".*"
- elif char == "?":
- re_pat += "."
- else:
- re_pat += re.escape(char)
- return r'(?ms)' + re_pat + r'\Z'
- class ResolverError(RuntimeError):
- def __init__(self, node, child, msg):
- """Resolve Error at `node` handling `child`."""
- super(ResolverError, self).__init__(msg)
- self.node = node
- self.child = child
- class ChildResolverError(ResolverError):
- def __init__(self, node, child, pathattr):
- """Child Resolve Error at `node` handling `child`."""
- names = [repr(_getattr(c, pathattr)) for c in node.children]
- msg = "%r has no child %s. Children are: %s."
- msg = msg % (node, child, ", ".join(names))
- super(ChildResolverError, self).__init__(node, child, msg)
- def _getattr(node, name):
- return getattr(node, name, None)
|