Skip to content

Commit

Permalink
Merge branch '3.x.x' of github.com:c0fec0de/anytree
Browse files Browse the repository at this point in the history
  • Loading branch information
c0fec0de committed Oct 11, 2023
2 parents 5d4e7b9 + a00f7f9 commit 1a49d5e
Show file tree
Hide file tree
Showing 9 changed files with 433 additions and 226 deletions.
15 changes: 9 additions & 6 deletions anytree/iterators/levelorderiter.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,14 @@ def _iter(children, filter_, stop, maxlevel):
level = 1
while children:
next_children = []
for child in children:
if filter_(child):
yield child
next_children += AbstractIter._get_children(child.children, stop)
children = next_children
level += 1
if AbstractIter._abort_at_level(level, maxlevel):
break
for child in children:
if filter_(child):
yield child
else:
for child in children:
if filter_(child):
yield child
next_children += AbstractIter._get_children(child.children, stop)
children = next_children
50 changes: 33 additions & 17 deletions anytree/render.py
Original file line number Diff line number Diff line change
Expand Up @@ -276,13 +276,14 @@ def __iter__(self):

def __next(self, node, continues, level=0):
yield RenderTree.__item(node, continues, self.style)
children = node.children
level += 1
if children and (self.maxlevel is None or level < self.maxlevel):
children = self.childiter(children)
for child, is_last in _is_last(children):
for grandchild in self.__next(child, continues + (not is_last,), level=level):
yield grandchild
if self.maxlevel is None or level < self.maxlevel:
children = node.children
if children:
children = self.childiter(children)
for child, is_last in _is_last(children):
for grandchild in self.__next(child, continues + (not is_last,), level=level):
yield grandchild

@staticmethod
def __item(node, continues, style):
Expand All @@ -296,8 +297,14 @@ def __item(node, continues, style):
return Row(pre, fill, node)

def __str__(self):
lines = ["%s%r" % (pre, node) for pre, _, node in self]
return "\n".join(lines)
def get():
for row in self:
lines = repr(row.node).splitlines() or [""]
yield "%s%s" % (row.pre, lines[0])
for line in lines[1:]:
yield "%s%s" % (row.fill, line)

return "\n".join(get())

def __repr__(self):
classname = self.__class__.__name__
Expand Down Expand Up @@ -332,19 +339,28 @@ def by_attr(self, attrname="name"):
"""

def get():
for pre, fill, node in self:
attr = attrname(node) if callable(attrname) else getattr(node, attrname, "")
if isinstance(attr, (list, tuple)):
lines = attr
else:
lines = str(attr).split("\n")
yield "%s%s" % (pre, lines[0])
for line in lines[1:]:
yield "%s%s" % (fill, line)
if callable(attrname):
for row in self:
attr = attrname(row.node)
yield from _format_row_any(row, attr)
else:
for row in self:
attr = getattr(row.node, attrname, "")
yield from _format_row_any(row, attr)

return "\n".join(get())


def _format_row_any(row, attr):
if isinstance(attr, (list, tuple)):
lines = attr or [""]
else:
lines = str(attr).splitlines() or [""]
yield "%s%s" % (row.pre, lines[0])
for line in lines[1:]:
yield "%s%s" % (row.fill, line)


def _is_last(iterable):
iter_ = iter(iterable)
try:
Expand Down
143 changes: 95 additions & 48 deletions anytree/resolver.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@

import re

from anytree.iterators.preorderiter import PreOrderIter

from .config import ASSERTIONS

_MAXCACHE = 20
Expand All @@ -17,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 @@ -41,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 @@ -87,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 @@ -114,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 @@ -138,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 @@ -196,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 @@ -207,26 +239,41 @@ def __start(self, node, path, cmp_):
def __glob(self, node, parts):
if ASSERTIONS: # pragma: no branch
assert node is not None
nodes = []
if parts:
name = parts[0]
remainder = parts[1:]
# handle relative
if name == "..":
parent = node.parent
if parent is None:
raise RootResolverError(node)
nodes += self.__glob(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
else:
nodes = [node]
return nodes

if not parts:
return [node]

name = parts[0]
remainder = parts[1:]

# handle relative
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)

# handle recursive
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 = []
Expand Down

0 comments on commit 1a49d5e

Please sign in to comment.