Skip to content

Commit

Permalink
fix #221, add relax attribute to Resolver (#232)
Browse files Browse the repository at this point in the history
  • Loading branch information
c0fec0de committed Oct 11, 2023
1 parent 6fe3851 commit 38a5ef1
Show file tree
Hide file tree
Showing 2 changed files with 245 additions and 76 deletions.
90 changes: 61 additions & 29 deletions anytree/resolver.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,16 @@ class Resolver:
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):
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):
"""
Expand All @@ -43,43 +45,50 @@ def get(self, node, path):
A resolver using the `name` attribute:
>>> r = Resolver('name')
>>> resolver = Resolver('name')
>>> relaxedresolver = Resolver('name', relax=True) # never generate exceptions
Relative paths:
>>> r.get(top, "sub0/sub0sub0")
>>> resolver.get(top, "sub0/sub0sub0")
Node('/top/sub0/sub0sub0')
>>> r.get(sub1, "..")
>>> resolver.get(sub1, "..")
Node('/top')
>>> r.get(sub1, "../sub0/sub0sub1")
>>> resolver.get(sub1, "../sub0/sub0sub1")
Node('/top/sub0/sub0sub1')
>>> r.get(sub1, ".")
>>> resolver.get(sub1, ".")
Node('/top/sub1')
>>> r.get(sub1, "")
>>> resolver.get(sub1, "")
Node('/top/sub1')
>>> r.get(top, "sub2")
>>> 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:
>>> r.get(sub0sub0, "/top")
>>> resolver.get(sub0sub0, "/top")
Node('/top')
>>> r.get(sub0sub0, "/top/sub0")
>>> resolver.get(sub0sub0, "/top/sub0")
Node('/top/sub0')
>>> r.get(sub0sub0, "/")
>>> resolver.get(sub0sub0, "/")
Traceback (most recent call last):
...
anytree.resolver.ResolverError: root node missing. root is '/top'.
>>> r.get(sub0sub0, "/bar")
>>> 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`:
>>> r.get(top, "..")
>>> resolver.get(top, "..")
Traceback (most recent call last):
...
anytree.resolver.RootResolverError: Cannot go above root node Node('/top')
Expand All @@ -89,20 +98,24 @@ def get(self, node, path):
Case insensitive matching:
>>> r.get(top, '/TOP')
>>> resolver.get(top, '/TOP')
Traceback (most recent call last):
...
anytree.resolver.ResolverError: unknown root node '/TOP'. root is '/top'.
>>> r = Resolver('name', ignorecase=True)
>>> r.get(top, '/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 ("", "."):
Expand All @@ -116,6 +129,8 @@ def __get(self, node, 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):
Expand All @@ -140,53 +155,64 @@ def glob(self, node, path):
A resolver using the `name` attribute:
>>> r = Resolver('name')
>>> resolver = Resolver('name')
>>> relaxedresolver = Resolver('name', relax=True) # never generate exceptions
Relative paths:
>>> r.glob(top, "sub0/sub?")
>>> resolver.glob(top, "sub0/sub?")
[Node('/top/sub0/sub0'), Node('/top/sub0/sub1')]
>>> r.glob(sub1, ".././*")
>>> resolver.glob(sub1, ".././*")
[Node('/top/sub0'), Node('/top/sub1')]
>>> r.glob(top, "*/*")
>>> resolver.glob(top, "*/*")
[Node('/top/sub0/sub0'), Node('/top/sub0/sub1'), Node('/top/sub1/sub0')]
>>> r.glob(top, "*/sub0")
>>> resolver.glob(top, "*/sub0")
[Node('/top/sub0/sub0'), Node('/top/sub1/sub0')]
>>> r.glob(top, "sub1/sub1")
>>> 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:
>>> r.glob(top, "bar*")
>>> resolver.glob(top, "bar*")
[]
>>> r.glob(top, "sub2")
>>> 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:
>>> r.glob(sub0sub0, "/top/*")
>>> resolver.glob(sub0sub0, "/top/*")
[Node('/top/sub0'), Node('/top/sub1')]
>>> r.glob(sub0sub0, "/")
>>> resolver.glob(sub0sub0, "/")
Traceback (most recent call last):
...
anytree.resolver.ResolverError: root node missing. root is '/top'.
>>> r.glob(sub0sub0, "/bar")
>>> 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`:
>>> r.glob(top, "..")
>>> 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_):
Expand All @@ -198,9 +224,13 @@ def __start(self, node, path, cmp_):
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)
Expand All @@ -220,6 +250,8 @@ def __glob(self, node, parts):
if name == "..":
parent = node.parent
if parent is None:
if self.relax:
return []
raise RootResolverError(node)
return self.__glob(parent, remainder)

Expand All @@ -239,7 +271,7 @@ def __glob(self, node, parts):
return matches

matches = self.__find(node, name, remainder)
if not matches and not Resolver.is_wildcard(name):
if not matches and not Resolver.is_wildcard(name) and not self.relax:
raise ChildResolverError(node, name, self.pathattr)
return matches

Expand Down

0 comments on commit 38a5ef1

Please sign in to comment.