|
|
|
|
|
|
|
from __future__ import print_function |
|
|
|
import re |
|
|
|
from anytree.iterators.preorderiter import PreOrderIter |
|
|
|
from .config import ASSERTIONS |
|
|
|
_MAXCACHE = 20 |
|
|
|
|
|
class Resolver: |
|
""" |
|
Resolve :any:`NodeMixin` paths using attribute `pathattr`. |
|
|
|
Keyword Args: |
|
name (str): Name of the node attribute to be used for resolving |
|
ignorecase (bool): Enable case insensisitve handling. |
|
relax (bool): Do not raise an exception. |
|
""" |
|
|
|
_match_cache = {} |
|
|
|
def __init__(self, pathattr="name", ignorecase=False, relax=False): |
|
super(Resolver, self).__init__() |
|
self.pathattr = pathattr |
|
self.ignorecase = ignorecase |
|
self.relax = relax |
|
|
|
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: |
|
|
|
>>> resolver = Resolver('name') |
|
>>> relaxedresolver = Resolver('name', relax=True) # never generate exceptions |
|
|
|
Relative paths: |
|
|
|
>>> resolver.get(top, "sub0/sub0sub0") |
|
Node('/top/sub0/sub0sub0') |
|
>>> resolver.get(sub1, "..") |
|
Node('/top') |
|
>>> resolver.get(sub1, "../sub0/sub0sub1") |
|
Node('/top/sub0/sub0sub1') |
|
>>> resolver.get(sub1, ".") |
|
Node('/top/sub1') |
|
>>> resolver.get(sub1, "") |
|
Node('/top/sub1') |
|
>>> resolver.get(top, "sub2") |
|
Traceback (most recent call last): |
|
... |
|
anytree.resolver.ChildResolverError: Node('/top') has no child sub2. Children are: 'sub0', 'sub1'. |
|
>>> print(relaxedresolver.get(top, "sub2")) |
|
None |
|
|
|
Absolute paths: |
|
|
|
>>> resolver.get(sub0sub0, "/top") |
|
Node('/top') |
|
>>> resolver.get(sub0sub0, "/top/sub0") |
|
Node('/top/sub0') |
|
>>> resolver.get(sub0sub0, "/") |
|
Traceback (most recent call last): |
|
... |
|
anytree.resolver.ResolverError: root node missing. root is '/top'. |
|
>>> print(relaxedresolver.get(sub0sub0, "/")) |
|
None |
|
>>> resolver.get(sub0sub0, "/bar") |
|
Traceback (most recent call last): |
|
... |
|
anytree.resolver.ResolverError: unknown root node '/bar'. root is '/top'. |
|
>>> print(relaxedresolver.get(sub0sub0, "/bar")) |
|
None |
|
|
|
Going above the root node raises a :any:`RootResolverError`: |
|
|
|
>>> resolver.get(top, "..") |
|
Traceback (most recent call last): |
|
... |
|
anytree.resolver.RootResolverError: Cannot go above root node Node('/top') |
|
|
|
.. note:: Please not that :any:`get()` returned `None` in exactly that case above, |
|
which was a bug until version 1.8.1. |
|
|
|
Case insensitive matching: |
|
|
|
>>> resolver.get(top, '/TOP') |
|
Traceback (most recent call last): |
|
... |
|
anytree.resolver.ResolverError: unknown root node '/TOP'. root is '/top'. |
|
|
|
>>> ignorecaseresolver = Resolver('name', ignorecase=True) |
|
>>> ignorecaseresolver.get(top, '/TOp') |
|
Node('/top') |
|
""" |
|
node, parts = self.__start(node, path, self.__cmp) |
|
if node is None and self.relax: |
|
return None |
|
for part in parts: |
|
if part == "..": |
|
parent = node.parent |
|
if parent is None: |
|
if self.relax: |
|
return None |
|
raise RootResolverError(node) |
|
node = parent |
|
elif part in ("", "."): |
|
pass |
|
else: |
|
node = self.__get(node, part) |
|
return node |
|
|
|
def __get(self, node, name): |
|
namestr = str(name) |
|
for child in node.children: |
|
if self.__cmp(_getattr(child, self.pathattr), namestr): |
|
return child |
|
if self.relax: |
|
return None |
|
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: |
|
|
|
>>> resolver = Resolver('name') |
|
>>> relaxedresolver = Resolver('name', relax=True) # never generate exceptions |
|
|
|
Relative paths: |
|
|
|
>>> resolver.glob(top, "sub0/sub?") |
|
[Node('/top/sub0/sub0'), Node('/top/sub0/sub1')] |
|
>>> resolver.glob(sub1, ".././*") |
|
[Node('/top/sub0'), Node('/top/sub1')] |
|
>>> resolver.glob(top, "*/*") |
|
[Node('/top/sub0/sub0'), Node('/top/sub0/sub1'), Node('/top/sub1/sub0')] |
|
>>> resolver.glob(top, "*/sub0") |
|
[Node('/top/sub0/sub0'), Node('/top/sub1/sub0')] |
|
>>> resolver.glob(top, "sub1/sub1") |
|
Traceback (most recent call last): |
|
... |
|
anytree.resolver.ChildResolverError: Node('/top/sub1') has no child sub1. Children are: 'sub0'. |
|
>>> relaxedresolver.glob(top, "sub1/sub1") |
|
[] |
|
|
|
Non-matching wildcards are no error: |
|
|
|
>>> resolver.glob(top, "bar*") |
|
[] |
|
>>> resolver.glob(top, "sub2") |
|
Traceback (most recent call last): |
|
... |
|
anytree.resolver.ChildResolverError: Node('/top') has no child sub2. Children are: 'sub0', 'sub1'. |
|
>>> relaxedresolver.glob(top, "sub2") |
|
[] |
|
|
|
Absolute paths: |
|
|
|
>>> resolver.glob(sub0sub0, "/top/*") |
|
[Node('/top/sub0'), Node('/top/sub1')] |
|
>>> resolver.glob(sub0sub0, "/") |
|
Traceback (most recent call last): |
|
... |
|
anytree.resolver.ResolverError: root node missing. root is '/top'. |
|
>>> relaxedresolver.glob(sub0sub0, "/") |
|
[] |
|
>>> resolver.glob(sub0sub0, "/bar") |
|
Traceback (most recent call last): |
|
... |
|
anytree.resolver.ResolverError: unknown root node '/bar'. root is '/top'. |
|
|
|
Going above the root node raises a :any:`RootResolverError`: |
|
|
|
>>> resolver.glob(top, "..") |
|
Traceback (most recent call last): |
|
... |
|
anytree.resolver.RootResolverError: Cannot go above root node Node('/top') |
|
>>> relaxedresolver.glob(top, "..") |
|
[] |
|
""" |
|
node, parts = self.__start(node, path, self.__match) |
|
if node is None and self.relax: |
|
return [] |
|
return self.__glob(node, parts) |
|
|
|
def __start(self, node, path, cmp_): |
|
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]: |
|
if self.relax: |
|
return None, None |
|
msg = "root node missing. root is '%s%s'." |
|
raise ResolverError(node, "", msg % (sep, str(rootpart))) |
|
if not cmp_(rootpart, parts[0]): |
|
if self.relax: |
|
return None, None |
|
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): |
|
if ASSERTIONS: |
|
assert node is not None |
|
|
|
if not parts: |
|
return [node] |
|
|
|
name = parts[0] |
|
remainder = parts[1:] |
|
|
|
|
|
if name == "..": |
|
parent = node.parent |
|
if parent is None: |
|
if self.relax: |
|
return [] |
|
raise RootResolverError(node) |
|
return self.__glob(parent, remainder) |
|
|
|
if name in ("", "."): |
|
return self.__glob(node, remainder) |
|
|
|
|
|
if name == "**": |
|
matches = [] |
|
for subnode in PreOrderIter(node): |
|
try: |
|
for match in self.__glob(subnode, remainder): |
|
if match not in matches: |
|
matches.append(match) |
|
except ChildResolverError: |
|
pass |
|
return matches |
|
|
|
matches = self.__find(node, name, remainder) |
|
if not matches and not Resolver.is_wildcard(name) and not self.relax: |
|
raise ChildResolverError(node, name, self.pathattr) |
|
return matches |
|
|
|
def __find(self, node, pat, remainder): |
|
matches = [] |
|
for child in node.children: |
|
name = _getattr(child, self.pathattr) |
|
try: |
|
if self.__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 |
|
|
|
def __match(self, name, pat): |
|
k = (pat, self.ignorecase) |
|
try: |
|
re_pat = Resolver._match_cache[k] |
|
except KeyError: |
|
res = Resolver.__translate(pat) |
|
if len(Resolver._match_cache) >= _MAXCACHE: |
|
Resolver._match_cache.clear() |
|
flags = 0 |
|
if self.ignorecase: |
|
flags |= re.IGNORECASE |
|
Resolver._match_cache[k] = re_pat = re.compile(res, flags=flags) |
|
return re_pat.match(name) is not None |
|
|
|
def __cmp(self, name, pat): |
|
if self.ignorecase: |
|
return name.upper() == pat.upper() |
|
return name == pat |
|
|
|
@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 RootResolverError(ResolverError): |
|
def __init__(self, root): |
|
"""Root Resolve Error, cannot go above root node.""" |
|
msg = "Cannot go above root node %r" % (root,) |
|
super(RootResolverError, self).__init__(root, None, msg) |
|
|
|
|
|
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." % (node, child, ", ".join(names)) |
|
super(ChildResolverError, self).__init__(node, child, msg) |
|
|
|
|
|
def _getattr(node, name): |
|
return str(getattr(node, name, None)) |
|
|