Source code for anytree.resolver

# -*- coding: utf-8 -*-


from __future__ import print_function
try:  # pragma: no cover
    from collections import OrderedDict
except ImportError:  # pragma: no cover
    from ordereddict import OrderedDict

import re

_MAXCACHE = 20


[docs]class Resolver(object): _match_cache = {} def __init__(self, pathattr='name'): """Resolve :any:`NodeMixin` paths using attribute `pathattr`.""" super(Resolver, self).__init__() self.pathattr = pathattr
[docs] 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: nodemap = self.__get_nodemap(node) try: node = nodemap[part] except KeyError: raise ChildResolverError(node, part, nodemap.keys()) return node
[docs] 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): parts = path.split("/") if path.startswith("/"): node = node.root rootpart = self.__get_part(node) parts.pop(0) if not parts[0]: msg = "root node missing. root is '/%s'." raise ResolverError(node, "", msg % str(rootpart)) elif parts[0] != rootpart: msg = "unknown root node '/%s'. root is '/%s'." raise ResolverError(node, "", msg % (parts[0], str(rootpart))) parts.pop(0) return node, parts def __get_nodemap(self, node): subnodes = [(self.__get_part(child), child) for child in node.children] return OrderedDict(subnodes) def __glob(self, node, parts): nodes = [] part = parts[0] remainparts = parts[1:] # handle relative if part == "..": nodes += self.__glob(node.parent, remainparts) elif part in ("", "."): nodes += self.__glob(node, remainparts) else: matches = [] nodemap = self.__get_nodemap(node) for name, child in nodemap.items(): try: if Resolver.__match(name, part): if remainparts: matches += self.__glob(child, remainparts) else: matches.append(child) except ResolverError as exc: if not Resolver.__is_wildcard(part): raise exc if not matches and not Resolver.__is_wildcard(part): raise ChildResolverError(node, part, nodemap.keys()) nodes += matches return nodes def __get_part(self, node): return getattr(node, self.pathattr) @staticmethod def __is_wildcard(pat): return "?" in pat or "*" in pat @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 + '\Z(?ms)'
[docs]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
[docs]class ChildResolverError(ResolverError): def __init__(self, node, child, children): """Child Resolve Error at `node` handling `child` with known `children`.""" names = ", ".join([repr(c) for c in children]) msg = "%r has no child %s. Children are: %s." msg = msg % (node, child, names) super(ChildResolverError, self).__init__(node, child, msg)