# -*- 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 _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 = _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 = _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 re_pat + r'\Z(?ms)'
|
|
|
|
|
|
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)
|