Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix #221, add relax attribute to Resolver #232

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading