pax_global_header00006660000000000000000000000064145255071230014516gustar00rootroot0000000000000052 comment=65a5d09ce0a592a80918f094ec3fa48b7faca250 anytree-2.12.1/000077500000000000000000000000001452550712300132505ustar00rootroot00000000000000anytree-2.12.1/.github/000077500000000000000000000000001452550712300146105ustar00rootroot00000000000000anytree-2.12.1/.github/workflows/000077500000000000000000000000001452550712300166455ustar00rootroot00000000000000anytree-2.12.1/.github/workflows/main.yml000066400000000000000000000027401452550712300203170ustar00rootroot00000000000000name: test on: [push, pull_request, release] jobs: build: strategy: matrix: python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12"] runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - name: Install dependencies run: | python -m pip install --upgrade pip sudo apt-get install -y graphviz pip install tox "poetry>=1.4" coveralls - name: TOX run: tox - name: Upload coverage data to coveralls.io run: coveralls --service=github env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} COVERALLS_FLAG_NAME: ${{ matrix.test-name }} COVERALLS_PARALLEL: true coveralls: name: Indicate completion to coveralls.io needs: build runs-on: ubuntu-latest container: python:3-slim steps: - name: Finished run: | pip3 install --upgrade coveralls coveralls --service=github --finish env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} publish: if: github.event_name == 'push' && github.ref_type == 'tag' needs: coveralls runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Build and publish to pypi uses: JRubics/poetry-publish@v1.17 with: pypi_token: ${{ secrets.PYPI_TOKEN }}anytree-2.12.1/.gitignore000066400000000000000000000001741452550712300152420ustar00rootroot00000000000000*.egg-info/ *.xml .coverage .tox .venv .mypy_cache /.vscode __pycache__ build dist/ docs/_readthedocs/ poetry.lock setup.py anytree-2.12.1/.readthedocs.yaml000066400000000000000000000007531452550712300165040ustar00rootroot00000000000000# Read the Docs configuration file # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details # Required version: 2 # Set the version of Python and other tools you might need build: os: ubuntu-22.04 tools: python: "3.9" commands: - pip install --upgrade --no-cache-dir pip - pip install --no-cache-dir "poetry>=1.4" "crashtest==0.4.1" - poetry install --with=doc --without=test - poetry run make html -C docs - cp -r docs/build _readthedocs anytree-2.12.1/CONTRIBUTING.md000066400000000000000000000021761452550712300155070ustar00rootroot00000000000000# Contribute ## Branches * `2.x.x` main line for `2.x.x` * `3.x.x` main line for `3.x.x` * documentation links refer to `3.x.x` * 2.x.x can be merged to 3.x.x * `main` * 2.x.x can be merged to main ## Testing ### Create Environment Run these commands just the first time: ```bash # Ensure python3 is installed python3 -m venv .venv source .venv/bin/activate pip install tox "poetry>=1.4" "crashtest==0.4.1" ``` ### Enter Environment Run this command once you open a new shell: ```bash source .venv/bin/activate ``` ### Test Your Changes ```bash # test tox ``` ### Release ```bash # Update 3.x.x git checkout 3.x.x git pull origin 2.x.x git push origin 3.x.x prev_version=$(poetry version -s) # Version Bump poetry version minor # OR poetry version patch # Commit, Tag and Push version=$(poetry version -s) sed "s/$prev_version/$version/g" -i anytree/__init__.py git commit -m"version bump to ${version}" pyproject.toml anytree/__init__.py git tag "${version}" -m "Release ${version}" git push git push --tags # Update main git checkout main git pull origin 2.x.x git push origin main # Publishing is handled by CI ``` anytree-2.12.1/LICENSE000066400000000000000000000261351452550712300142640ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "{}" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright {yyyy} {name of copyright owner} Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. anytree-2.12.1/README.rst000066400000000000000000000161011452550712300147360ustar00rootroot00000000000000.. image:: https://badge.fury.io/py/anytree.svg :target: https://badge.fury.io/py/anytree .. image:: https://img.shields.io/pypi/dm/anytree.svg?label=pypi%20downloads :target: https://pypi.python.org/pypi/anytree .. image:: https://readthedocs.org/projects/anytree/badge/?version=latest :target: https://anytree.readthedocs.io/en/latest/?badge=latest .. image:: https://coveralls.io/repos/github/c0fec0de/anytree/badge.svg :target: https://coveralls.io/github/c0fec0de/anytree .. image:: https://readthedocs.org/projects/anytree/badge/?version=stable :target: https://anytree.readthedocs.io/en/stable .. image:: https://api.codeclimate.com/v1/badges/e6d325d6fd23a93aab20/maintainability :target: https://codeclimate.com/github/c0fec0de/anytree/maintainability :alt: Maintainability .. image:: https://img.shields.io/pypi/pyversions/anytree.svg :target: https://pypi.python.org/pypi/anytree .. image:: https://img.shields.io/badge/code%20style-pep8-brightgreen.svg :target: https://www.python.org/dev/peps/pep-0008/ .. image:: https://img.shields.io/badge/code%20style-pep257-brightgreen.svg :target: https://www.python.org/dev/peps/pep-0257/ .. image:: https://img.shields.io/badge/linter-pylint-%231674b1?style=flat :target: https://www.pylint.org/ .. image:: https://img.shields.io/badge/code%20style-black-000000.svg :target: https://github.com/psf/black .. image:: https://img.shields.io/github/contributors/c0fec0de/anytree.svg :target: https://github.com/c0fec0de/anytree/graphs/contributors/ .. image:: https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square :target: http://makeapullrequest.com .. image:: https://img.shields.io/github/issues-pr/c0fec0de/anytree.svg :target: https://github.com/c0fec0de/anytree/pulls .. image:: https://img.shields.io/github/issues-pr-closed/c0fec0de/anytree.svg :target: https://github.com/c0fec0de/anytree/pulls?q=is%3Apr+is%3Aclosed Links ===== * Documentation_ * PyPI_ * GitHub_ * Changelog_ * Issues_ * Contributors_ * If you enjoy anytree_ .. image:: https://github.com/c0fec0de/anytree/raw/devel/docs/static/buymeacoffee.png :target: https://www.buymeacoffee.com/1oYX0sw .. _anytree: https://anytree.readthedocs.io/en/stable/ .. _Documentation: https://anytree.readthedocs.io/en/stable/ .. _PyPI: https://pypi.org/project/anytree .. _GitHub: https://github.com/c0fec0de/anytree .. _Changelog: https://github.com/c0fec0de/anytree/releases .. _Issues: https://github.com/c0fec0de/anytree/issues .. _Contributors: https://github.com/c0fec0de/anytree/graphs/contributors .. _Node: https://anytree.readthedocs.io/en/stable/api/anytree.node.html#anytree.node.node.Node .. _RenderTree: https://anytree.readthedocs.io/en/stable/api/anytree.render.html#anytree.render.RenderTree .. _UniqueDotExporter: https://anytree.readthedocs.io/en/stable/exporter/dotexporter.html#anytree.exporter.dotexporter.UniqueDotExporter .. _NodeMixin: https://anytree.readthedocs.io/en/stable/api/anytree.node.html#anytree.node.nodemixin.NodeMixin .. _Importers: https://anytree.readthedocs.io/en/stable/importer.html .. _Exporters: https://anytree.readthedocs.io/en/stable/exporter.html Getting started --------------- .. _getting_started: Usage is simple. **Construction** >>> from anytree import Node, RenderTree >>> udo = Node("Udo") >>> marc = Node("Marc", parent=udo) >>> lian = Node("Lian", parent=marc) >>> dan = Node("Dan", parent=udo) >>> jet = Node("Jet", parent=dan) >>> jan = Node("Jan", parent=dan) >>> joe = Node("Joe", parent=dan) **Node** >>> print(udo) Node('/Udo') >>> print(joe) Node('/Udo/Dan/Joe') **Tree** >>> for pre, fill, node in RenderTree(udo): ... print("%s%s" % (pre, node.name)) Udo ├── Marc │ └── Lian └── Dan ├── Jet ├── Jan └── Joe For details see Node_ and RenderTree_. **Visualization** >>> from anytree.exporter import UniqueDotExporter >>> # graphviz needs to be installed for the next line! >>> UniqueDotExporter(udo).to_picture("udo.png") .. image:: https://anytree.readthedocs.io/en/latest/_images/udo.png The UniqueDotExporter_ can be started at any node and has various formatting hookups: >>> UniqueDotExporter(dan, ... nodeattrfunc=lambda node: "fixedsize=true, width=1, height=1, shape=diamond", ... edgeattrfunc=lambda parent, child: "style=bold" ... ).to_picture("dan.png") .. image:: https://anytree.readthedocs.io/en/latest/_images/dan.png There are various other Importers_ and Exporters_. **Manipulation** A second tree: >>> mary = Node("Mary") >>> urs = Node("Urs", parent=mary) >>> chris = Node("Chris", parent=mary) >>> marta = Node("Marta", parent=mary) >>> print(RenderTree(mary)) Node('/Mary') ├── Node('/Mary/Urs') ├── Node('/Mary/Chris') └── Node('/Mary/Marta') Append: >>> udo.parent = mary >>> print(RenderTree(mary)) Node('/Mary') ├── Node('/Mary/Urs') ├── Node('/Mary/Chris') ├── Node('/Mary/Marta') └── Node('/Mary/Udo') ├── Node('/Mary/Udo/Marc') │ └── Node('/Mary/Udo/Marc/Lian') └── Node('/Mary/Udo/Dan') ├── Node('/Mary/Udo/Dan/Jet') ├── Node('/Mary/Udo/Dan/Jan') └── Node('/Mary/Udo/Dan/Joe') Subtree rendering: >>> print(RenderTree(marc)) Node('/Mary/Udo/Marc') └── Node('/Mary/Udo/Marc/Lian') Cut: >>> dan.parent = None >>> print(RenderTree(dan)) Node('/Dan') ├── Node('/Dan/Jet') ├── Node('/Dan/Jan') └── Node('/Dan/Joe') **Extending any python class to become a tree node** The entire tree magic is encapsulated by NodeMixin_ add it as base class and the class becomes a tree node: >>> from anytree import NodeMixin, RenderTree >>> class MyBaseClass(object): # Just an example of a base class ... foo = 4 >>> class MyClass(MyBaseClass, NodeMixin): # Add Node feature ... def __init__(self, name, length, width, parent=None, children=None): ... super(MyClass, self).__init__() ... self.name = name ... self.length = length ... self.width = width ... self.parent = parent ... if children: ... self.children = children Just set the `parent` attribute to reflect the tree relation: >>> my0 = MyClass('my0', 0, 0) >>> my1 = MyClass('my1', 1, 0, parent=my0) >>> my2 = MyClass('my2', 0, 2, parent=my0) >>> for pre, fill, node in RenderTree(my0): ... treestr = u"%s%s" % (pre, node.name) ... print(treestr.ljust(8), node.length, node.width) my0 0 0 ├── my1 1 0 └── my2 0 2 The `children` can be used likewise: >>> my0 = MyClass('my0', 0, 0, children=[ ... MyClass('my1', 1, 0), ... MyClass('my2', 0, 2), ... ]) >>> for pre, fill, node in RenderTree(my0): ... treestr = u"%s%s" % (pre, node.name) ... print(treestr.ljust(8), node.length, node.width) my0 0 0 ├── my1 1 0 └── my2 0 2 Documentation ------------- Please see the Documentation_ for all details. Installation ------------ To install the `anytree` module run:: pip install anytree If you do not have write-permissions to the python installation, try:: pip install anytree --user anytree-2.12.1/anytree.sublime-project000066400000000000000000000010771452550712300177520ustar00rootroot00000000000000{ "folders": [ { "path": "." } ], "settings": { "python_interpreter": "python3", "rulers": [ 79, 119 ], "tab_size": 4, "translate_tabs_to_spaces": true, "trim_trailing_white_space_on_save": true, "default_encoding": "UTF-8", "SublimeLinter.linters.pycodestyle.args": [ "--config=$project_path/setup.cfg" ], "SublimeLinter.linters.pydocstyle.args": [ "--config=$project_path/setup.cfg" ] } } anytree-2.12.1/anytree/000077500000000000000000000000001452550712300147175ustar00rootroot00000000000000anytree-2.12.1/anytree/__init__.py000066400000000000000000000031331452550712300170300ustar00rootroot00000000000000# -*- coding: utf-8 -*- """Powerful and Lightweight Python Tree Data Structure.""" __version__ = "2.12.1" __author__ = "c0fec0de" __author_email__ = "c0fec0de@gmail.com" __description__ = """Powerful and Lightweight Python Tree Data Structure.""" __url__ = "https://github.com/c0fec0de/anytree" from . import cachedsearch # noqa from . import util # noqa from .iterators import LevelOrderGroupIter # noqa from .iterators import LevelOrderIter # noqa from .iterators import PostOrderIter # noqa from .iterators import PreOrderIter # noqa from .iterators import ZigZagGroupIter # noqa from .node import AnyNode # noqa from .node import LightNodeMixin # noqa from .node import LoopError # noqa from .node import Node # noqa from .node import NodeMixin # noqa from .node import SymlinkNode # noqa from .node import SymlinkNodeMixin # noqa from .node import TreeError # noqa from .render import AbstractStyle # noqa from .render import AsciiStyle # noqa from .render import ContRoundStyle # noqa from .render import ContStyle # noqa from .render import DoubleStyle # noqa from .render import RenderTree # noqa from .resolver import ChildResolverError # noqa from .resolver import Resolver # noqa from .resolver import ResolverError # noqa from .resolver import RootResolverError # noqa from .search import CountError # noqa from .search import find # noqa from .search import find_by_attr # noqa from .search import findall # noqa from .search import findall_by_attr # noqa from .walker import Walker # noqa from .walker import WalkError # noqa # legacy LevelGroupOrderIter = LevelOrderGroupIter anytree-2.12.1/anytree/cachedsearch.py000066400000000000000000000031061452550712300176660ustar00rootroot00000000000000""" Node Searching with Cache. .. note:: These functions require https://pypi.org/project/fastcache/, otherwise caching is not active. """ from . import search # fastcache is optional try: from fastcache import clru_cache as _cache except ImportError: from functools import wraps # dummy decorator which does NOT cache def _cache(size): # pylint: disable=W0613 def decorator(func): @wraps(func) def wrapped(*args, **kwargs): return func(*args, **kwargs) return wrapped return decorator CACHE_SIZE = 32 @_cache(CACHE_SIZE) def findall(node, filter_=None, stop=None, maxlevel=None, mincount=None, maxcount=None): """Identical to :any:`search.findall` but cached.""" return search.findall(node, filter_=filter_, stop=stop, maxlevel=maxlevel, mincount=mincount, maxcount=maxcount) @_cache(CACHE_SIZE) def findall_by_attr(node, value, name="name", maxlevel=None, mincount=None, maxcount=None): """Identical to :any:`search.findall_by_attr` but cached.""" return search.findall_by_attr(node, value, name=name, maxlevel=maxlevel, mincount=mincount, maxcount=maxcount) @_cache(CACHE_SIZE) def find(node, filter_=None, stop=None, maxlevel=None): """Identical to :any:`search.find` but cached.""" return search.find(node, filter_=filter_, stop=stop, maxlevel=maxlevel) @_cache(CACHE_SIZE) def find_by_attr(node, value, name="name", maxlevel=None): """Identical to :any:`search.find_by_attr` but cached.""" return search.find_by_attr(node, value, name=name, maxlevel=maxlevel) anytree-2.12.1/anytree/config.py000066400000000000000000000002401452550712300165320ustar00rootroot00000000000000"""Central Configuration.""" import os # Global Option which enables all internal assertions. ASSERTIONS = bool(int(os.environ.get("ANYTREE_ASSERTIONS", 0))) anytree-2.12.1/anytree/dotexport.py000066400000000000000000000006541452550712300173260ustar00rootroot00000000000000import warnings from anytree.exporter.dotexporter import DotExporter class RenderTreeGraph(DotExporter): def __init__(self, *args, **kwargs): """Legacy. Use :any:`anytree.exporter.DotExporter` instead.""" warnings.warn( ("anytree.RenderTreeGraph has moved. Use anytree.exporter.DotExporter instead"), DeprecationWarning ) super(RenderTreeGraph, self).__init__(*args, **kwargs) anytree-2.12.1/anytree/exporter/000077500000000000000000000000001452550712300165675ustar00rootroot00000000000000anytree-2.12.1/anytree/exporter/__init__.py000066400000000000000000000004041452550712300206760ustar00rootroot00000000000000"""Exporter.""" from .dictexporter import DictExporter # noqa from .dotexporter import DotExporter # noqa from .dotexporter import UniqueDotExporter # noqa from .jsonexporter import JsonExporter # noqa from .mermaidexporter import MermaidExporter # noqa anytree-2.12.1/anytree/exporter/dictexporter.py000066400000000000000000000057761452550712300216740ustar00rootroot00000000000000class DictExporter: """ Tree to dictionary exporter. Every node is converted to a dictionary with all instance attributes as key-value pairs. Child nodes are exported to the children attribute. A list of dictionaries. Keyword Args: dictcls: class used as dictionary. :any:`dict` by default. attriter: attribute iterator for sorting and/or filtering. childiter: child iterator for sorting and/or filtering. maxlevel (int): Limit export to this number of levels. >>> from pprint import pprint # just for nice printing >>> from anytree import AnyNode >>> from anytree.exporter import DictExporter >>> root = AnyNode(a="root") >>> s0 = AnyNode(a="sub0", parent=root) >>> s0a = AnyNode(a="sub0A", b="foo", parent=s0) >>> s0b = AnyNode(a="sub0B", parent=s0) >>> s1 = AnyNode(a="sub1", parent=root) >>> exporter = DictExporter() >>> pprint(exporter.export(root)) {'a': 'root', 'children': [{'a': 'sub0', 'children': [{'a': 'sub0A', 'b': 'foo'}, {'a': 'sub0B'}]}, {'a': 'sub1'}]} The attribute iterator `attriter` may be used for filtering too. For example, just dump attributes named `a`: >>> exporter = DictExporter(attriter=lambda attrs: [(k, v) for k, v in attrs if k == "a"]) >>> pprint(exporter.export(root)) {'a': 'root', 'children': [{'a': 'sub0', 'children': [{'a': 'sub0A'}, {'a': 'sub0B'}]}, {'a': 'sub1'}]} The child iterator `childiter` can be used for sorting and filtering likewise: >>> exporter = DictExporter(childiter=lambda children: [child for child in children if "0" in child.a]) >>> pprint(exporter.export(root)) {'a': 'root', 'children': [{'a': 'sub0', 'children': [{'a': 'sub0A', 'b': 'foo'}, {'a': 'sub0B'}]}]} """ def __init__(self, dictcls=dict, attriter=None, childiter=list, maxlevel=None): self.dictcls = dictcls self.attriter = attriter self.childiter = childiter self.maxlevel = maxlevel def export(self, node): """Export tree starting at `node`.""" attriter = self.attriter or (lambda attr_values: attr_values) return self.__export(node, self.dictcls, attriter, self.childiter) def __export(self, node, dictcls, attriter, childiter, level=1): attr_values = attriter(self._iter_attr_values(node)) data = dictcls(attr_values) maxlevel = self.maxlevel if maxlevel is None or level < maxlevel: children = [ self.__export(child, dictcls, attriter, childiter, level=level + 1) for child in childiter(node.children) ] if children: data["children"] = children return data @staticmethod def _iter_attr_values(node): # pylint: disable=C0103 for k, v in node.__dict__.items(): if k in ("_NodeMixin__children", "_NodeMixin__parent"): continue yield k, v anytree-2.12.1/anytree/exporter/dotexporter.py000066400000000000000000000360271452550712300215300ustar00rootroot00000000000000import codecs import itertools import logging import re from os import path, remove from subprocess import check_call from tempfile import NamedTemporaryFile import six from anytree import PreOrderIter _RE_ESC = re.compile(r'["\\]') class DotExporter: """ Dot Language Exporter. Args: node (Node): start node. Keyword Args: graph: DOT graph type. name: DOT graph name. options: list of options added to the graph. indent (int): number of spaces for indent. nodenamefunc: Function to extract node name from `node` object. The function shall accept one `node` object as argument and return the name of it. nodeattrfunc: Function to decorate a node with attributes. The function shall accept one `node` object as argument and return the attributes. edgeattrfunc: Function to decorate a edge with attributes. The function shall accept two `node` objects as argument. The first the node and the second the child and return the attributes. edgetypefunc: Function to which gives the edge type. The function shall accept two `node` objects as argument. The first the node and the second the child and return the edge (i.e. '->'). filter_: Function to filter nodes to include in export. The function shall accept one `node` object as argument and return True if it should be included, or False if it should not be included. stop: stop iteration at `node` if `stop` function returns `True` for `node`. maxlevel (int): Limit export to this number of levels. >>> from anytree import Node >>> root = Node("root") >>> s0 = Node("sub0", parent=root, edge=2) >>> s0b = Node("sub0B", parent=s0, foo=4, edge=109) >>> s0a = Node("sub0A", parent=s0, edge="") >>> s1 = Node("sub1", parent=root, edge="") >>> s1a = Node("sub1A", parent=s1, edge=7) >>> s1b = Node("sub1B", parent=s1, edge=8) >>> s1c = Node("sub1C", parent=s1, edge=22) >>> s1ca = Node("sub1Ca", parent=s1c, edge=42) .. note:: If the node names are not unqiue, see :any:`UniqueDotExporter`. A directed graph: >>> from anytree.exporter import DotExporter >>> for line in DotExporter(root): ... print(line) digraph tree { "root"; "sub0"; "sub0B"; "sub0A"; "sub1"; "sub1A"; "sub1B"; "sub1C"; "sub1Ca"; "root" -> "sub0"; "root" -> "sub1"; "sub0" -> "sub0B"; "sub0" -> "sub0A"; "sub1" -> "sub1A"; "sub1" -> "sub1B"; "sub1" -> "sub1C"; "sub1C" -> "sub1Ca"; } The resulting graph: .. image:: ../static/dotexporter0.png An undirected graph: >>> def nodenamefunc(node): ... return '%s:%s' % (node.name, node.depth) >>> def edgeattrfunc(node, child): ... return 'label="%s:%s"' % (node.name, child.name) >>> def edgetypefunc(node, child): ... return '--' >>> from anytree.exporter import DotExporter >>> for line in DotExporter(root, graph="graph", ... nodenamefunc=nodenamefunc, ... nodeattrfunc=lambda node: "shape=box", ... edgeattrfunc=edgeattrfunc, ... edgetypefunc=edgetypefunc): ... print(line) graph tree { "root:0" [shape=box]; "sub0:1" [shape=box]; "sub0B:2" [shape=box]; "sub0A:2" [shape=box]; "sub1:1" [shape=box]; "sub1A:2" [shape=box]; "sub1B:2" [shape=box]; "sub1C:2" [shape=box]; "sub1Ca:3" [shape=box]; "root:0" -- "sub0:1" [label="root:sub0"]; "root:0" -- "sub1:1" [label="root:sub1"]; "sub0:1" -- "sub0B:2" [label="sub0:sub0B"]; "sub0:1" -- "sub0A:2" [label="sub0:sub0A"]; "sub1:1" -- "sub1A:2" [label="sub1:sub1A"]; "sub1:1" -- "sub1B:2" [label="sub1:sub1B"]; "sub1:1" -- "sub1C:2" [label="sub1:sub1C"]; "sub1C:2" -- "sub1Ca:3" [label="sub1C:sub1Ca"]; } The resulting graph: .. image:: ../static/dotexporter1.png To export custom node implementations or :any:`AnyNode`, please provide a proper `nodenamefunc`: >>> from anytree import AnyNode >>> root = AnyNode(id="root") >>> s0 = AnyNode(id="sub0", parent=root) >>> s0b = AnyNode(id="s0b", parent=s0) >>> s0a = AnyNode(id="s0a", parent=s0) >>> from anytree.exporter import DotExporter >>> for line in DotExporter(root, nodenamefunc=lambda n: n.id): ... print(line) digraph tree { "root"; "sub0"; "s0b"; "s0a"; "root" -> "sub0"; "sub0" -> "s0b"; "sub0" -> "s0a"; } """ def __init__( self, node, graph="digraph", name="tree", options=None, indent=4, nodenamefunc=None, nodeattrfunc=None, edgeattrfunc=None, edgetypefunc=None, filter_=None, maxlevel=None, stop=None, ): self.node = node self.graph = graph self.name = name self.options = options self.indent = indent self.nodenamefunc = nodenamefunc self.nodeattrfunc = nodeattrfunc self.edgeattrfunc = edgeattrfunc self.edgetypefunc = edgetypefunc self.filter_ = filter_ self.maxlevel = maxlevel self.stop = stop def __iter__(self): # prepare indent = " " * self.indent nodenamefunc = self.nodenamefunc or self._default_nodenamefunc nodeattrfunc = self.nodeattrfunc or self._default_nodeattrfunc edgeattrfunc = self.edgeattrfunc or self._default_edgeattrfunc edgetypefunc = self.edgetypefunc or self._default_edgetypefunc filter_ = self.filter_ or self._default_filter return self.__iter(indent, nodenamefunc, nodeattrfunc, edgeattrfunc, edgetypefunc, filter_) @staticmethod def _default_nodenamefunc(node): return node.name @staticmethod def _default_nodeattrfunc(node): # pylint: disable=unused-argument return None @staticmethod def _default_edgeattrfunc(node, child): # pylint: disable=unused-argument return None @staticmethod def _default_edgetypefunc(node, child): # pylint: disable=unused-argument return "->" @staticmethod def _default_filter(node): # pylint: disable=unused-argument return True def __iter(self, indent, nodenamefunc, nodeattrfunc, edgeattrfunc, edgetypefunc, filter_): yield "{self.graph} {self.name} {{".format(self=self) for option in self.__iter_options(indent): yield option for node in self.__iter_nodes(indent, nodenamefunc, nodeattrfunc, filter_): yield node for edge in self.__iter_edges(indent, nodenamefunc, edgeattrfunc, edgetypefunc, filter_): yield edge yield "}" def __iter_options(self, indent): options = self.options if options: for option in options: yield "%s%s" % (indent, option) def __iter_nodes(self, indent, nodenamefunc, nodeattrfunc, filter_): for node in PreOrderIter(self.node, filter_=filter_, stop=self.stop, maxlevel=self.maxlevel): nodename = nodenamefunc(node) nodeattr = nodeattrfunc(node) nodeattr = " [%s]" % nodeattr if nodeattr is not None else "" yield '%s"%s"%s;' % (indent, DotExporter.esc(nodename), nodeattr) def __iter_edges(self, indent, nodenamefunc, edgeattrfunc, edgetypefunc, filter_): maxlevel = self.maxlevel - 1 if self.maxlevel else None for node in PreOrderIter(self.node, filter_=filter_, stop=self.stop, maxlevel=maxlevel): nodename = nodenamefunc(node) for child in node.children: if not filter_(child): continue childname = nodenamefunc(child) edgeattr = edgeattrfunc(node, child) edgetype = edgetypefunc(node, child) edgeattr = " [%s]" % edgeattr if edgeattr is not None else "" yield '%s"%s" %s "%s"%s;' % ( indent, DotExporter.esc(nodename), edgetype, DotExporter.esc(childname), edgeattr, ) def to_dotfile(self, filename): """ Write graph to `filename`. >>> from anytree import Node >>> root = Node("root") >>> s0 = Node("sub0", parent=root) >>> s0b = Node("sub0B", parent=s0) >>> s0a = Node("sub0A", parent=s0) >>> s1 = Node("sub1", parent=root) >>> s1a = Node("sub1A", parent=s1) >>> s1b = Node("sub1B", parent=s1) >>> s1c = Node("sub1C", parent=s1) >>> s1ca = Node("sub1Ca", parent=s1c) >>> from anytree.exporter import DotExporter >>> DotExporter(root).to_dotfile("tree.dot") The generated file should be handed over to the `dot` tool from the http://www.graphviz.org/ package:: $ dot tree.dot -T png -o tree.png """ with codecs.open(filename, "w", "utf-8") as file: for line in self: file.write("%s\n" % line) def to_picture(self, filename): """ Write graph to a temporary file and invoke `dot`. The output file type is automatically detected from the file suffix. *`graphviz` needs to be installed, before usage of this method.* """ fileformat = path.splitext(filename)[1][1:] with NamedTemporaryFile("wb", delete=False) as dotfile: dotfilename = dotfile.name for line in self: dotfile.write(("%s\n" % line).encode("utf-8")) dotfile.flush() cmd = ["dot", dotfilename, "-T", fileformat, "-o", filename] check_call(cmd) try: remove(dotfilename) # pylint: disable=broad-exception-caught except Exception: # pragma: no cover logging.getLogger(__name__).warning("Could not remove temporary file %s", dotfilename) @staticmethod def esc(value): """Escape Strings.""" return _RE_ESC.sub(lambda m: r"\%s" % m.group(0), six.text_type(value)) class UniqueDotExporter(DotExporter): """ Unqiue Dot Language Exporter. Handle trees with random or conflicting node names gracefully. Args: node (Node): start node. Keyword Args: graph: DOT graph type. name: DOT graph name. options: list of options added to the graph. indent (int): number of spaces for indent. nodenamefunc: Function to extract node name from `node` object. The function shall accept one `node` object as argument and return the name of it. nodeattrfunc: Function to decorate a node with attributes. The function shall accept one `node` object as argument and return the attributes. edgeattrfunc: Function to decorate a edge with attributes. The function shall accept two `node` objects as argument. The first the node and the second the child and return the attributes. edgetypefunc: Function to which gives the edge type. The function shall accept two `node` objects as argument. The first the node and the second the child and return the edge (i.e. '->'). filter_: Function to filter nodes to include in export. The function shall accept one `node` object as argument and return True if it should be included, or False if it should not be included. stop: stop iteration at `node` if `stop` function returns `True` for `node`. maxlevel (int): Limit export to this number of levels. >>> from anytree import Node >>> root = Node("root") >>> s0 = Node("sub0", parent=root) >>> s0b = Node("s0", parent=s0) >>> s0a = Node("s0", parent=s0) >>> s1 = Node("sub1", parent=root) >>> s1a = Node("s1", parent=s1) >>> s1b = Node("s1", parent=s1) >>> s1c = Node("s1", parent=s1) >>> s1ca = Node("sub1Ca", parent=s1c) >>> from anytree.exporter import UniqueDotExporter >>> for line in UniqueDotExporter(root): ... print(line) digraph tree { "0x0" [label="root"]; "0x1" [label="sub0"]; "0x2" [label="s0"]; "0x3" [label="s0"]; "0x4" [label="sub1"]; "0x5" [label="s1"]; "0x6" [label="s1"]; "0x7" [label="s1"]; "0x8" [label="sub1Ca"]; "0x0" -> "0x1"; "0x0" -> "0x4"; "0x1" -> "0x2"; "0x1" -> "0x3"; "0x4" -> "0x5"; "0x4" -> "0x6"; "0x4" -> "0x7"; "0x7" -> "0x8"; } The resulting graph: .. image:: ../static/uniquedotexporter2.png To export custom node implementations or :any:`AnyNode`, please provide a proper `nodeattrfunc`: >>> from anytree import AnyNode >>> root = AnyNode(id="root") >>> s0 = AnyNode(id="sub0", parent=root) >>> s0b = AnyNode(id="s0", parent=s0) >>> s0a = AnyNode(id="s0", parent=s0) >>> from anytree.exporter import UniqueDotExporter >>> for line in UniqueDotExporter(root, nodeattrfunc=lambda n: 'label="%s"' % (n.id)): ... print(line) digraph tree { "0x0" [label="root"]; "0x1" [label="sub0"]; "0x2" [label="s0"]; "0x3" [label="s0"]; "0x0" -> "0x1"; "0x1" -> "0x2"; "0x1" -> "0x3"; } """ def __init__( self, node, graph="digraph", name="tree", options=None, indent=4, nodenamefunc=None, nodeattrfunc=None, edgeattrfunc=None, edgetypefunc=None, filter_=None, stop=None, maxlevel=None, ): super(UniqueDotExporter, self).__init__( node, graph=graph, name=name, options=options, indent=indent, nodenamefunc=nodenamefunc, nodeattrfunc=nodeattrfunc, edgeattrfunc=edgeattrfunc, edgetypefunc=edgetypefunc, filter_=filter_, stop=stop, maxlevel=maxlevel, ) self.__node_ids = {} self.__node_counter = itertools.count() # pylint: disable=arguments-differ def _default_nodenamefunc(self, node): node_id = id(node) try: num = self.__node_ids[node_id] except KeyError: num = self.__node_ids[node_id] = next(self.__node_counter) return hex(num) @staticmethod def _default_nodeattrfunc(node): return 'label="%s"' % (node.name,) anytree-2.12.1/anytree/exporter/jsonexporter.py000066400000000000000000000042301452550712300217020ustar00rootroot00000000000000import json from .dictexporter import DictExporter class JsonExporter: """ Tree to JSON exporter. The tree is converted to a dictionary via `dictexporter` and exported to JSON. Keyword Arguments: dictexporter: Dictionary Exporter used (see :any:`DictExporter`). maxlevel (int): Limit export to this number of levels. kwargs: All other arguments are passed to :any:`json.dump`/:any:`json.dumps`. See documentation for reference. >>> from anytree import AnyNode >>> from anytree.exporter import JsonExporter >>> root = AnyNode(a="root") >>> s0 = AnyNode(a="sub0", parent=root) >>> s0a = AnyNode(a="sub0A", b="foo", parent=s0) >>> s0b = AnyNode(a="sub0B", parent=s0) >>> s1 = AnyNode(a="sub1", parent=root) >>> exporter = JsonExporter(indent=2, sort_keys=True) >>> print(exporter.export(root)) { "a": "root", "children": [ { "a": "sub0", "children": [ { "a": "sub0A", "b": "foo" }, { "a": "sub0B" } ] }, { "a": "sub1" } ] } .. note:: Whenever the json output does not meet your expections, see the :any:`json` documentation. For instance, if you have unicode/ascii issues, please try `JsonExporter(..., ensure_ascii=False)`. """ def __init__(self, dictexporter=None, maxlevel=None, **kwargs): self.dictexporter = dictexporter self.maxlevel = maxlevel self.kwargs = kwargs def _export(self, node): dictexporter = self.dictexporter or DictExporter() if self.maxlevel is not None: dictexporter.maxlevel = self.maxlevel return dictexporter.export(node) def export(self, node): """Return JSON for tree starting at `node`.""" data = self._export(node) return json.dumps(data, **self.kwargs) def write(self, node, filehandle): """Write JSON to `filehandle` starting at `node`.""" data = self._export(node) return json.dump(data, filehandle, **self.kwargs) anytree-2.12.1/anytree/exporter/mermaidexporter.py000066400000000000000000000165631452550712300223630ustar00rootroot00000000000000import codecs import itertools import re import six from anytree import PreOrderIter _RE_ESC = re.compile(r'["\\]') class MermaidExporter: """ Mermaid Exporter. Args: node (Node): start node. Keyword Args: graph: Mermaid graph type. name: Mermaid graph name. options: list of options added to the graph. indent (int): number of spaces for indent. nodenamefunc: Function to extract node name from `node` object. The function shall accept one `node` object as argument and return the name of it. Returns a unique identifier by default. nodefunc: Function to decorate a node with attributes. The function shall accept one `node` object as argument and return the attributes. Returns ``[{node.name}]`` and creates therefore a rectangular node by default. edgefunc: Function to decorate a edge with attributes. The function shall accept two `node` objects as argument. The first the node and the second the child and return edge. Returns ``-->`` by default. filter_: Function to filter nodes to include in export. The function shall accept one `node` object as argument and return True if it should be included, or False if it should not be included. stop: stop iteration at `node` if `stop` function returns `True` for `node`. maxlevel (int): Limit export to this number of levels. >>> from anytree import Node >>> root = Node("root") >>> s0 = Node("sub0", parent=root, edge=2) >>> s0b = Node("sub0B", parent=s0, foo=4, edge=109) >>> s0a = Node("sub0A", parent=s0, edge="") >>> s1 = Node("sub1", parent=root, edge="") >>> s1a = Node("sub1A", parent=s1, edge=7) >>> s1b = Node("sub1B", parent=s1, edge=8) >>> s1c = Node("sub1C", parent=s1, edge=22) >>> s1ca = Node("sub1Ca", parent=s1c, edge=42) A top-down graph: >>> from anytree.exporter import MermaidExporter >>> for line in MermaidExporter(root): ... print(line) graph TD N0["root"] N1["sub0"] N2["sub0B"] N3["sub0A"] N4["sub1"] N5["sub1A"] N6["sub1B"] N7["sub1C"] N8["sub1Ca"] N0-->N1 N0-->N4 N1-->N2 N1-->N3 N4-->N5 N4-->N6 N4-->N7 N7-->N8 A customized graph with round boxes and named arrows: >>> def nodefunc(node): ... return '("%s")' % (node.name) >>> def edgefunc(node, child): ... return f"--{child.edge}-->" >>> options = [ ... "%% just an example comment", ... "%% could be an option too", ... ] >>> for line in MermaidExporter(root, options=options, nodefunc=nodefunc, edgefunc=edgefunc): ... print(line) graph TD %% just an example comment %% could be an option too N0("root") N1("sub0") N2("sub0B") N3("sub0A") N4("sub1") N5("sub1A") N6("sub1B") N7("sub1C") N8("sub1Ca") N0--2-->N1 N0---->N4 N1--109-->N2 N1---->N3 N4--7-->N5 N4--8-->N6 N4--22-->N7 N7--42-->N8 """ def __init__( self, node, graph="graph", name="TD", options=None, indent=0, nodenamefunc=None, nodefunc=None, edgefunc=None, filter_=None, stop=None, maxlevel=None, ): self.node = node self.graph = graph self.name = name self.options = options self.indent = indent self.nodenamefunc = nodenamefunc self.nodefunc = nodefunc self.edgefunc = edgefunc self.filter_ = filter_ self.stop = stop self.maxlevel = maxlevel self.__node_ids = {} self.__node_counter = itertools.count() def __iter__(self): # prepare indent = " " * self.indent nodenamefunc = self.nodenamefunc or self._default_nodenamefunc nodefunc = self.nodefunc or self._default_nodefunc edgefunc = self.edgefunc or self._default_edgefunc filter_ = self.filter_ or (lambda node: True) stop = self.stop or (lambda node: False) return self.__iter(indent, nodenamefunc, nodefunc, edgefunc, filter_, stop) # pylint: disable=arguments-differ def _default_nodenamefunc(self, node): node_id = id(node) try: num = self.__node_ids[node_id] except KeyError: num = self.__node_ids[node_id] = next(self.__node_counter) return "N%d" % (num,) @staticmethod def _default_nodefunc(node): # pylint: disable=W0613 return '["%s"]' % (MermaidExporter.esc(node.name),) @staticmethod def _default_edgefunc(node, child): # pylint: disable=W0613 return "-->" def __iter(self, indent, nodenamefunc, nodefunc, edgefunc, filter_, stop): yield "{self.graph} {self.name}".format(self=self) for option in self.__iter_options(indent): yield option for node in self.__iter_nodes(indent, nodenamefunc, nodefunc, filter_, stop): yield node for edge in self.__iter_edges(indent, nodenamefunc, edgefunc, filter_, stop): yield edge def __iter_options(self, indent): options = self.options if options: for option in options: yield "%s%s" % (indent, option) def __iter_nodes(self, indent, nodenamefunc, nodefunc, filter_, stop): for node in PreOrderIter(self.node, filter_=filter_, stop=stop, maxlevel=self.maxlevel): nodename = nodenamefunc(node) node = nodefunc(node) yield "%s%s%s" % (indent, nodename, node) def __iter_edges(self, indent, nodenamefunc, edgefunc, filter_, stop): maxlevel = self.maxlevel - 1 if self.maxlevel else None for node in PreOrderIter(self.node, filter_=filter_, stop=stop, maxlevel=maxlevel): nodename = nodenamefunc(node) for child in node.children: if filter_(child) and not stop(child): childname = nodenamefunc(child) edge = edgefunc(node, child) yield "%s%s%s%s" % ( indent, nodename, edge, childname, ) def to_file(self, filename): """ Write graph to `filename`. >>> from anytree import Node >>> root = Node("root") >>> s0 = Node("sub0", parent=root) >>> s0b = Node("sub0B", parent=s0) >>> s0a = Node("sub0A", parent=s0) >>> s1 = Node("sub1", parent=root) >>> s1a = Node("sub1A", parent=s1) >>> s1b = Node("sub1B", parent=s1) >>> s1c = Node("sub1C", parent=s1) >>> s1ca = Node("sub1Ca", parent=s1c) >>> from anytree.exporter import MermaidExporter >>> MermaidExporter(root).to_file("tree.md") """ with codecs.open(filename, "w", "utf-8") as file: file.write("```mermaid\n") for line in self: file.write("%s\n" % line) file.write("```") @staticmethod def esc(value): """Escape Strings.""" return _RE_ESC.sub(lambda m: r"\%s" % m.group(0), six.text_type(value)) anytree-2.12.1/anytree/importer/000077500000000000000000000000001452550712300165605ustar00rootroot00000000000000anytree-2.12.1/anytree/importer/__init__.py000066400000000000000000000001571452550712300206740ustar00rootroot00000000000000"""Importer.""" from .dictimporter import DictImporter # noqa from .jsonimporter import JsonImporter # noqa anytree-2.12.1/anytree/importer/dictimporter.py000066400000000000000000000027641452550712300216500ustar00rootroot00000000000000# -*- coding: utf-8 -*- from anytree import AnyNode from ..config import ASSERTIONS class DictImporter: """ Import Tree from dictionary. Every dictionary is converted to an instance of `nodecls`. The dictionaries listed in the children attribute are converted likewise and added as children. Keyword Args: nodecls: class used for nodes. >>> from anytree.importer import DictImporter >>> from anytree import RenderTree >>> importer = DictImporter() >>> data = { ... 'a': 'root', ... 'children': [{'a': 'sub0', ... 'children': [{'a': 'sub0A', 'b': 'foo'}, {'a': 'sub0B'}]}, ... {'a': 'sub1'}]} >>> root = importer.import_(data) >>> print(RenderTree(root)) AnyNode(a='root') ├── AnyNode(a='sub0') │ ├── AnyNode(a='sub0A', b='foo') │ └── AnyNode(a='sub0B') └── AnyNode(a='sub1') """ def __init__(self, nodecls=AnyNode): self.nodecls = nodecls def import_(self, data): """Import tree from `data`.""" return self.__import(data) def __import(self, data, parent=None): if ASSERTIONS: # pragma: no branch assert isinstance(data, dict) assert "parent" not in data attrs = dict(data) children = attrs.pop("children", []) node = self.nodecls(parent=parent, **attrs) for child in children: self.__import(child, parent=node) return node anytree-2.12.1/anytree/importer/jsonimporter.py000066400000000000000000000033611452550712300216700ustar00rootroot00000000000000# -*- coding: utf-8 -*- import json from .dictimporter import DictImporter class JsonImporter: """ Import Tree from JSON. The JSON is read and converted to a dictionary via `dictimporter`. Keyword Arguments: dictimporter: Dictionary Importer used (see :any:`DictImporter`). kwargs: All other arguments are passed to :any:`json.load`/:any:`json.loads`. See documentation for reference. >>> from anytree.importer import JsonImporter >>> from anytree import RenderTree >>> importer = JsonImporter() >>> data = ''' ... { ... "a": "root", ... "children": [ ... { ... "a": "sub0", ... "children": [ ... { ... "a": "sub0A", ... "b": "foo" ... }, ... { ... "a": "sub0B" ... } ... ] ... }, ... { ... "a": "sub1" ... } ... ] ... }''' >>> root = importer.import_(data) >>> print(RenderTree(root)) AnyNode(a='root') ├── AnyNode(a='sub0') │ ├── AnyNode(a='sub0A', b='foo') │ └── AnyNode(a='sub0B') └── AnyNode(a='sub1') """ def __init__(self, dictimporter=None, **kwargs): self.dictimporter = dictimporter self.kwargs = kwargs def __import(self, data): dictimporter = self.dictimporter or DictImporter() return dictimporter.import_(data) def import_(self, data): """Read JSON from `data`.""" return self.__import(json.loads(data, **self.kwargs)) def read(self, filehandle): """Read JSON from `filehandle`.""" return self.__import(json.load(filehandle, **self.kwargs)) anytree-2.12.1/anytree/iterators/000077500000000000000000000000001452550712300167335ustar00rootroot00000000000000anytree-2.12.1/anytree/iterators/__init__.py000066400000000000000000000014461452550712300210510ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ Tree Iteration. * :any:`PreOrderIter`: iterate over tree using pre-order strategy (self, children) * :any:`PostOrderIter`: iterate over tree using post-order strategy (children, self) * :any:`LevelOrderIter`: iterate over tree using level-order strategy * :any:`LevelOrderGroupIter`: iterate over tree using level-order strategy returning group for every level * :any:`ZigZagGroupIter`: iterate over tree using level-order strategy returning group for every level """ from .abstractiter import AbstractIter # noqa from .levelordergroupiter import LevelOrderGroupIter # noqa from .levelorderiter import LevelOrderIter # noqa from .postorderiter import PostOrderIter # noqa from .preorderiter import PreOrderIter # noqa from .zigzaggroupiter import ZigZagGroupIter # noqa anytree-2.12.1/anytree/iterators/abstractiter.py000066400000000000000000000033441452550712300220000ustar00rootroot00000000000000import six class AbstractIter(six.Iterator): # pylint: disable=R0205 """ Iterate over tree starting at `node`. Base class for all iterators. Keyword Args: filter_: function called with every `node` as argument, `node` is returned if `True`. stop: stop iteration at `node` if `stop` function returns `True` for `node`. maxlevel (int): maximum descending in the node hierarchy. """ def __init__(self, node, filter_=None, stop=None, maxlevel=None): self.node = node self.filter_ = filter_ self.stop = stop self.maxlevel = maxlevel self.__iter = None def __init(self): node = self.node maxlevel = self.maxlevel filter_ = self.filter_ or AbstractIter.__default_filter stop = self.stop or AbstractIter.__default_stop children = [] if AbstractIter._abort_at_level(1, maxlevel) else AbstractIter._get_children([node], stop) return self._iter(children, filter_, stop, maxlevel) @staticmethod def __default_filter(node): # pylint: disable=W0613 return True @staticmethod def __default_stop(node): # pylint: disable=W0613 return False def __iter__(self): return self def __next__(self): if self.__iter is None: self.__iter = self.__init() return next(self.__iter) @staticmethod def _iter(children, filter_, stop, maxlevel): raise NotImplementedError() # pragma: no cover @staticmethod def _abort_at_level(level, maxlevel): return maxlevel is not None and level > maxlevel @staticmethod def _get_children(children, stop): return [child for child in children if not stop(child)] anytree-2.12.1/anytree/iterators/levelordergroupiter.py000066400000000000000000000042341452550712300234140ustar00rootroot00000000000000from .abstractiter import AbstractIter class LevelOrderGroupIter(AbstractIter): """ Iterate over tree applying level-order strategy with grouping starting at `node`. Return a tuple of nodes for each level. The first tuple contains the nodes at level 0 (always `node`). The second tuple contains the nodes at level 1 (children of `node`). The next level contains the children of the children, and so on. >>> from anytree import Node, RenderTree, AsciiStyle, LevelOrderGroupIter >>> f = Node("f") >>> b = Node("b", parent=f) >>> a = Node("a", parent=b) >>> d = Node("d", parent=b) >>> c = Node("c", parent=d) >>> e = Node("e", parent=d) >>> g = Node("g", parent=f) >>> i = Node("i", parent=g) >>> h = Node("h", parent=i) >>> print(RenderTree(f, style=AsciiStyle()).by_attr()) f |-- b | |-- a | +-- d | |-- c | +-- e +-- g +-- i +-- h >>> [[node.name for node in children] for children in LevelOrderGroupIter(f)] [['f'], ['b', 'g'], ['a', 'd', 'i'], ['c', 'e', 'h']] >>> [[node.name for node in children] for children in LevelOrderGroupIter(f, maxlevel=3)] [['f'], ['b', 'g'], ['a', 'd', 'i']] >>> [[node.name for node in children] ... for children in LevelOrderGroupIter(f, filter_=lambda n: n.name not in ('e', 'g'))] [['f'], ['b'], ['a', 'd', 'i'], ['c', 'h']] >>> [[node.name for node in children] ... for children in LevelOrderGroupIter(f, stop=lambda n: n.name == 'd')] [['f'], ['b', 'g'], ['a', 'i'], ['h']] """ @staticmethod def _iter(children, filter_, stop, maxlevel): level = 1 while children: yield tuple(child for child in children if filter_(child)) level += 1 if AbstractIter._abort_at_level(level, maxlevel): break children = LevelOrderGroupIter._get_grandchildren(children, stop) @staticmethod def _get_grandchildren(children, stop): next_children = [] for child in children: next_children = next_children + AbstractIter._get_children(child.children, stop) return next_children anytree-2.12.1/anytree/iterators/levelorderiter.py000066400000000000000000000033141452550712300223350ustar00rootroot00000000000000from .abstractiter import AbstractIter class LevelOrderIter(AbstractIter): """ Iterate over tree applying level-order strategy starting at `node`. >>> from anytree import Node, RenderTree, AsciiStyle, LevelOrderIter >>> f = Node("f") >>> b = Node("b", parent=f) >>> a = Node("a", parent=b) >>> d = Node("d", parent=b) >>> c = Node("c", parent=d) >>> e = Node("e", parent=d) >>> g = Node("g", parent=f) >>> i = Node("i", parent=g) >>> h = Node("h", parent=i) >>> print(RenderTree(f, style=AsciiStyle()).by_attr()) f |-- b | |-- a | +-- d | |-- c | +-- e +-- g +-- i +-- h >>> [node.name for node in LevelOrderIter(f)] ['f', 'b', 'g', 'a', 'd', 'i', 'c', 'e', 'h'] >>> [node.name for node in LevelOrderIter(f, maxlevel=3)] ['f', 'b', 'g', 'a', 'd', 'i'] >>> [node.name for node in LevelOrderIter(f, filter_=lambda n: n.name not in ('e', 'g'))] ['f', 'b', 'a', 'd', 'i', 'c', 'h'] >>> [node.name for node in LevelOrderIter(f, stop=lambda n: n.name == 'd')] ['f', 'b', 'g', 'a', 'i', 'h'] """ @staticmethod def _iter(children, filter_, stop, maxlevel): level = 1 while children: next_children = [] level += 1 if AbstractIter._abort_at_level(level, maxlevel): 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 anytree-2.12.1/anytree/iterators/postorderiter.py000066400000000000000000000033221452550712300222120ustar00rootroot00000000000000from .abstractiter import AbstractIter class PostOrderIter(AbstractIter): """ Iterate over tree applying post-order strategy starting at `node`. >>> from anytree import Node, RenderTree, AsciiStyle, PostOrderIter >>> f = Node("f") >>> b = Node("b", parent=f) >>> a = Node("a", parent=b) >>> d = Node("d", parent=b) >>> c = Node("c", parent=d) >>> e = Node("e", parent=d) >>> g = Node("g", parent=f) >>> i = Node("i", parent=g) >>> h = Node("h", parent=i) >>> print(RenderTree(f, style=AsciiStyle()).by_attr()) f |-- b | |-- a | +-- d | |-- c | +-- e +-- g +-- i +-- h >>> [node.name for node in PostOrderIter(f)] ['a', 'c', 'e', 'd', 'b', 'h', 'i', 'g', 'f'] >>> [node.name for node in PostOrderIter(f, maxlevel=3)] ['a', 'd', 'b', 'i', 'g', 'f'] >>> [node.name for node in PostOrderIter(f, filter_=lambda n: n.name not in ('e', 'g'))] ['a', 'c', 'd', 'b', 'h', 'i', 'f'] >>> [node.name for node in PostOrderIter(f, stop=lambda n: n.name == 'd')] ['a', 'b', 'h', 'i', 'g', 'f'] """ @staticmethod def _iter(children, filter_, stop, maxlevel): return PostOrderIter.__next(children, 1, filter_, stop, maxlevel) @staticmethod def __next(children, level, filter_, stop, maxlevel): if not AbstractIter._abort_at_level(level, maxlevel): for child in children: grandchildren = AbstractIter._get_children(child.children, stop) for grandchild in PostOrderIter.__next(grandchildren, level + 1, filter_, stop, maxlevel): yield grandchild if filter_(child): yield child anytree-2.12.1/anytree/iterators/preorderiter.py000066400000000000000000000033121452550712300220120ustar00rootroot00000000000000from .abstractiter import AbstractIter class PreOrderIter(AbstractIter): """ Iterate over tree applying pre-order strategy starting at `node`. Start at root and go-down until reaching a leaf node. Step upwards then, and search for the next leafs. >>> from anytree import Node, RenderTree, AsciiStyle, PreOrderIter >>> f = Node("f") >>> b = Node("b", parent=f) >>> a = Node("a", parent=b) >>> d = Node("d", parent=b) >>> c = Node("c", parent=d) >>> e = Node("e", parent=d) >>> g = Node("g", parent=f) >>> i = Node("i", parent=g) >>> h = Node("h", parent=i) >>> print(RenderTree(f, style=AsciiStyle()).by_attr()) f |-- b | |-- a | +-- d | |-- c | +-- e +-- g +-- i +-- h >>> [node.name for node in PreOrderIter(f)] ['f', 'b', 'a', 'd', 'c', 'e', 'g', 'i', 'h'] >>> [node.name for node in PreOrderIter(f, maxlevel=3)] ['f', 'b', 'a', 'd', 'g', 'i'] >>> [node.name for node in PreOrderIter(f, filter_=lambda n: n.name not in ('e', 'g'))] ['f', 'b', 'a', 'd', 'c', 'i', 'h'] >>> [node.name for node in PreOrderIter(f, stop=lambda n: n.name == 'd')] ['f', 'b', 'a', 'g', 'i', 'h'] """ @staticmethod def _iter(children, filter_, stop, maxlevel): for child_ in children: if stop(child_): continue if filter_(child_): yield child_ if not AbstractIter._abort_at_level(2, maxlevel): descendantmaxlevel = maxlevel - 1 if maxlevel else None for descendant_ in PreOrderIter._iter(child_.children, filter_, stop, descendantmaxlevel): yield descendant_ anytree-2.12.1/anytree/iterators/zigzaggroupiter.py000066400000000000000000000041461452550712300225460ustar00rootroot00000000000000from ..config import ASSERTIONS from .abstractiter import AbstractIter from .levelordergroupiter import LevelOrderGroupIter class ZigZagGroupIter(AbstractIter): """ Iterate over tree applying Zig-Zag strategy with grouping starting at `node`. Return a tuple of nodes for each level. The first tuple contains the nodes at level 0 (always `node`). The second tuple contains the nodes at level 1 (children of `node`) in reversed order. The next level contains the children of the children in forward order, and so on. >>> from anytree import Node, RenderTree, AsciiStyle, ZigZagGroupIter >>> f = Node("f") >>> b = Node("b", parent=f) >>> a = Node("a", parent=b) >>> d = Node("d", parent=b) >>> c = Node("c", parent=d) >>> e = Node("e", parent=d) >>> g = Node("g", parent=f) >>> i = Node("i", parent=g) >>> h = Node("h", parent=i) >>> print(RenderTree(f, style=AsciiStyle()).by_attr()) f |-- b | |-- a | +-- d | |-- c | +-- e +-- g +-- i +-- h >>> [[node.name for node in children] for children in ZigZagGroupIter(f)] [['f'], ['g', 'b'], ['a', 'd', 'i'], ['h', 'e', 'c']] >>> [[node.name for node in children] for children in ZigZagGroupIter(f, maxlevel=3)] [['f'], ['g', 'b'], ['a', 'd', 'i']] >>> [[node.name for node in children] ... for children in ZigZagGroupIter(f, filter_=lambda n: n.name not in ('e', 'g'))] [['f'], ['b'], ['a', 'd', 'i'], ['h', 'c']] >>> [[node.name for node in children] ... for children in ZigZagGroupIter(f, stop=lambda n: n.name == 'd')] [['f'], ['g', 'b'], ['a', 'i'], ['h']] """ @staticmethod def _iter(children, filter_, stop, maxlevel): if children: if ASSERTIONS: # pragma: no branch assert len(children) == 1 _iter = LevelOrderGroupIter(children[0], filter_, stop, maxlevel) while True: try: yield next(_iter) yield tuple(reversed(next(_iter))) except StopIteration: break anytree-2.12.1/anytree/node/000077500000000000000000000000001452550712300156445ustar00rootroot00000000000000anytree-2.12.1/anytree/node/__init__.py000066400000000000000000000014631452550712300177610ustar00rootroot00000000000000""" Node Classes. * :any:`AnyNode`: a generic tree node with any number of attributes. * :any:`Node`: a simple tree node with at least a name attribute and any number of additional attributes. * :any:`NodeMixin`: extends any python class to a tree node. * :any:`SymlinkNode`: Tree node which references to another tree node. * :any:`SymlinkNodeMixin`: extends any Python class to a symbolic link to a tree node. * :any:`LightNodeMixin`: A :any:`NodeMixin` using slots. """ from .anynode import AnyNode # noqa from .exceptions import LoopError # noqa from .exceptions import TreeError # noqa from .lightnodemixin import LightNodeMixin # noqa from .node import Node # noqa from .nodemixin import NodeMixin # noqa from .symlinknode import SymlinkNode # noqa from .symlinknodemixin import SymlinkNodeMixin # noqa anytree-2.12.1/anytree/node/anynode.py000066400000000000000000000063071452550712300176610ustar00rootroot00000000000000# -*- coding: utf-8 -*- from .nodemixin import NodeMixin from .util import _repr class AnyNode(NodeMixin): """ A generic tree node with any `kwargs`. Keyword Args: parent: Reference to parent node. children: Iterable with child nodes. *: Any other given attribute is just stored as object attribute. Other than :any:`Node` this class has no default identifier. It is up to the user to use other attributes for identification. The `parent` attribute refers the parent node: >>> from anytree import AnyNode, RenderTree >>> root = AnyNode(id="root") >>> s0 = AnyNode(id="sub0", parent=root) >>> s0b = AnyNode(id="sub0B", parent=s0, foo=4, bar=109) >>> s0a = AnyNode(id="sub0A", parent=s0) >>> s1 = AnyNode(id="sub1", parent=root) >>> s1a = AnyNode(id="sub1A", parent=s1) >>> s1b = AnyNode(id="sub1B", parent=s1, bar=8) >>> s1c = AnyNode(id="sub1C", parent=s1) >>> s1ca = AnyNode(id="sub1Ca", parent=s1c) >>> root AnyNode(id='root') >>> s0 AnyNode(id='sub0') >>> print(RenderTree(root)) AnyNode(id='root') ├── AnyNode(id='sub0') │ ├── AnyNode(bar=109, foo=4, id='sub0B') │ └── AnyNode(id='sub0A') └── AnyNode(id='sub1') ├── AnyNode(id='sub1A') ├── AnyNode(bar=8, id='sub1B') └── AnyNode(id='sub1C') └── AnyNode(id='sub1Ca') >>> print(RenderTree(root)) AnyNode(id='root') ├── AnyNode(id='sub0') │ ├── AnyNode(bar=109, foo=4, id='sub0B') │ └── AnyNode(id='sub0A') └── AnyNode(id='sub1') ├── AnyNode(id='sub1A') ├── AnyNode(bar=8, id='sub1B') └── AnyNode(id='sub1C') └── AnyNode(id='sub1Ca') Node attributes can be added, modified and deleted the pythonic way: >>> root.new = 'a new attribute' >>> s0b AnyNode(bar=109, foo=4, id='sub0B') >>> s0b.bar = 110 # modified >>> s0b AnyNode(bar=110, foo=4, id='sub0B') >>> del s1b.bar >>> print(RenderTree(root)) AnyNode(id='root', new='a new attribute') ├── AnyNode(id='sub0') │ ├── AnyNode(bar=110, foo=4, id='sub0B') │ └── AnyNode(id='sub0A') └── AnyNode(id='sub1') ├── AnyNode(id='sub1A') ├── AnyNode(id='sub1B') └── AnyNode(id='sub1C') └── AnyNode(id='sub1Ca') The same tree can be constructed by using the `children` attribute: >>> root = AnyNode(id="root", children=[ ... AnyNode(id="sub0", children=[ ... AnyNode(id="sub0B", foo=4, bar=109), ... AnyNode(id="sub0A"), ... ]), ... AnyNode(id="sub1", children=[ ... AnyNode(id="sub1A"), ... AnyNode(id="sub1B", bar=8), ... AnyNode(id="sub1C", children=[ ... AnyNode(id="sub1Ca"), ... ]), ... ]), ... ]) """ def __init__(self, parent=None, children=None, **kwargs): self.__dict__.update(kwargs) self.parent = parent if children: self.children = children def __repr__(self): return _repr(self) anytree-2.12.1/anytree/node/exceptions.py000066400000000000000000000001721452550712300203770ustar00rootroot00000000000000class TreeError(RuntimeError): """Tree Error.""" class LoopError(TreeError): """Tree contains infinite loop.""" anytree-2.12.1/anytree/node/lightnodemixin.py000066400000000000000000000374261452550712300212540ustar00rootroot00000000000000# -*- coding: utf-8 -*- from anytree.iterators import PreOrderIter from ..config import ASSERTIONS from .exceptions import LoopError, TreeError class LightNodeMixin: """ The :any:`LightNodeMixin` behaves identical to :any:`NodeMixin`, but uses `__slots__`. There are some minor differences in the object behaviour. See slots_ for any details. .. _slots: https://docs.python.org/3/reference/datamodel.html#slots The only tree relevant information is the `parent` attribute. If `None` the :any:`LightNodeMixin` is root node. If set to another node, the :any:`LightNodeMixin` becomes the child of it. The `children` attribute can be used likewise. If `None` the :any:`LightNodeMixin` has no children. The `children` attribute can be set to any iterable of :any:`LightNodeMixin` instances. These instances become children of the node. >>> from anytree import LightNodeMixin, RenderTree >>> class MyBaseClass(): # Just an example of a base class ... __slots__ = [] >>> class MyClass(MyBaseClass, LightNodeMixin): # Add Node feature ... __slots__ = ['name', 'length', 'width'] ... def __init__(self, name, length, width, parent=None, children=None): ... super().__init__() ... self.name = name ... self.length = length ... self.width = width ... self.parent = parent ... if children: ... self.children = children Construction via `parent`: >>> my0 = MyClass('my0', 0, 0) >>> my1 = MyClass('my1', 1, 0, parent=my0) >>> my2 = MyClass('my2', 0, 2, parent=my0) >>> for pre, _, node in RenderTree(my0): ... treestr = u"%s%s" % (pre, node.name) ... print(treestr.ljust(8), node.length, node.width) my0 0 0 ├── my1 1 0 └── my2 0 2 Construction via `children`: >>> my0 = MyClass('my0', 0, 0, children=[ ... MyClass('my1', 1, 0), ... MyClass('my2', 0, 2), ... ]) >>> for pre, _, node in RenderTree(my0): ... treestr = u"%s%s" % (pre, node.name) ... print(treestr.ljust(8), node.length, node.width) my0 0 0 ├── my1 1 0 └── my2 0 2 Both approaches can be mixed: >>> my0 = MyClass('my0', 0, 0, children=[ ... MyClass('my1', 1, 0), ... ]) >>> my2 = MyClass('my2', 0, 2, parent=my0) >>> for pre, _, node in RenderTree(my0): ... treestr = u"%s%s" % (pre, node.name) ... print(treestr.ljust(8), node.length, node.width) my0 0 0 ├── my1 1 0 └── my2 0 2 """ __slots__ = ["__parent", "__children"] separator = "/" @property def parent(self): """ Parent Node. On set, the node is detached from any previous parent node and attached to the new node. >>> from anytree import Node, RenderTree >>> udo = Node("Udo") >>> marc = Node("Marc") >>> lian = Node("Lian", parent=marc) >>> print(RenderTree(udo)) Node('/Udo') >>> print(RenderTree(marc)) Node('/Marc') └── Node('/Marc/Lian') **Attach** >>> marc.parent = udo >>> print(RenderTree(udo)) Node('/Udo') └── Node('/Udo/Marc') └── Node('/Udo/Marc/Lian') **Detach** To make a node to a root node, just set this attribute to `None`. >>> marc.is_root False >>> marc.parent = None >>> marc.is_root True """ if hasattr(self, "_LightNodeMixin__parent"): return self.__parent return None @parent.setter def parent(self, value): if hasattr(self, "_LightNodeMixin__parent"): parent = self.__parent else: parent = None if parent is not value: self.__check_loop(value) self.__detach(parent) self.__attach(value) def __check_loop(self, node): if node is not None: if node is self: msg = "Cannot set parent. %r cannot be parent of itself." raise LoopError(msg % (self,)) if any(child is self for child in node.iter_path_reverse()): msg = "Cannot set parent. %r is parent of %r." raise LoopError(msg % (self, node)) def __detach(self, parent): # pylint: disable=W0212,W0238 if parent is not None: self._pre_detach(parent) parentchildren = parent.__children_or_empty if ASSERTIONS: # pragma: no branch assert any(child is self for child in parentchildren), "Tree is corrupt." # pragma: no cover # ATOMIC START parent.__children = [child for child in parentchildren if child is not self] self.__parent = None # ATOMIC END self._post_detach(parent) def __attach(self, parent): # pylint: disable=W0212 if parent is not None: self._pre_attach(parent) parentchildren = parent.__children_or_empty if ASSERTIONS: # pragma: no branch assert not any(child is self for child in parentchildren), "Tree is corrupt." # pragma: no cover # ATOMIC START parentchildren.append(self) self.__parent = parent # ATOMIC END self._post_attach(parent) @property def __children_or_empty(self): if not hasattr(self, "_LightNodeMixin__children"): self.__children = [] return self.__children @property def children(self): """ All child nodes. >>> from anytree import Node >>> n = Node("n") >>> a = Node("a", parent=n) >>> b = Node("b", parent=n) >>> c = Node("c", parent=n) >>> n.children (Node('/n/a'), Node('/n/b'), Node('/n/c')) Modifying the children attribute modifies the tree. **Detach** The children attribute can be updated by setting to an iterable. >>> n.children = [a, b] >>> n.children (Node('/n/a'), Node('/n/b')) Node `c` is removed from the tree. In case of an existing reference, the node `c` does not vanish and is the root of its own tree. >>> c Node('/c') **Attach** >>> d = Node("d") >>> d Node('/d') >>> n.children = [a, b, d] >>> n.children (Node('/n/a'), Node('/n/b'), Node('/n/d')) >>> d Node('/n/d') **Duplicate** A node can just be the children once. Duplicates cause a :any:`TreeError`: >>> n.children = [a, b, d, a] Traceback (most recent call last): ... anytree.node.exceptions.TreeError: Cannot add node Node('/n/a') multiple times as child. """ return tuple(self.__children_or_empty) @staticmethod def __check_children(children): seen = set() for child in children: childid = id(child) if childid not in seen: seen.add(childid) else: msg = "Cannot add node %r multiple times as child." % (child,) raise TreeError(msg) @children.setter def children(self, children): # convert iterable to tuple children = tuple(children) LightNodeMixin.__check_children(children) # ATOMIC start old_children = self.children del self.children try: self._pre_attach_children(children) for child in children: child.parent = self self._post_attach_children(children) if ASSERTIONS: # pragma: no branch assert len(self.children) == len(children) except Exception: self.children = old_children raise # ATOMIC end @children.deleter def children(self): children = self.children self._pre_detach_children(children) for child in self.children: child.parent = None if ASSERTIONS: # pragma: no branch assert len(self.children) == 0 self._post_detach_children(children) def _pre_detach_children(self, children): """Method call before detaching `children`.""" def _post_detach_children(self, children): """Method call after detaching `children`.""" def _pre_attach_children(self, children): """Method call before attaching `children`.""" def _post_attach_children(self, children): """Method call after attaching `children`.""" @property def path(self): """ Path from root node down to this `Node`. >>> from anytree import Node >>> udo = Node("Udo") >>> marc = Node("Marc", parent=udo) >>> lian = Node("Lian", parent=marc) >>> udo.path (Node('/Udo'),) >>> marc.path (Node('/Udo'), Node('/Udo/Marc')) >>> lian.path (Node('/Udo'), Node('/Udo/Marc'), Node('/Udo/Marc/Lian')) """ return self._path def iter_path_reverse(self): """ Iterate up the tree from the current node to the root node. >>> from anytree import Node >>> udo = Node("Udo") >>> marc = Node("Marc", parent=udo) >>> lian = Node("Lian", parent=marc) >>> for node in udo.iter_path_reverse(): ... print(node) Node('/Udo') >>> for node in marc.iter_path_reverse(): ... print(node) Node('/Udo/Marc') Node('/Udo') >>> for node in lian.iter_path_reverse(): ... print(node) Node('/Udo/Marc/Lian') Node('/Udo/Marc') Node('/Udo') """ node = self while node is not None: yield node node = node.parent @property def _path(self): return tuple(reversed(list(self.iter_path_reverse()))) @property def ancestors(self): """ All parent nodes and their parent nodes. >>> from anytree import Node >>> udo = Node("Udo") >>> marc = Node("Marc", parent=udo) >>> lian = Node("Lian", parent=marc) >>> udo.ancestors () >>> marc.ancestors (Node('/Udo'),) >>> lian.ancestors (Node('/Udo'), Node('/Udo/Marc')) """ if self.parent is None: return tuple() return self.parent.path @property def descendants(self): """ All child nodes and all their child nodes. >>> from anytree import Node >>> udo = Node("Udo") >>> marc = Node("Marc", parent=udo) >>> lian = Node("Lian", parent=marc) >>> loui = Node("Loui", parent=marc) >>> soe = Node("Soe", parent=lian) >>> udo.descendants (Node('/Udo/Marc'), Node('/Udo/Marc/Lian'), Node('/Udo/Marc/Lian/Soe'), Node('/Udo/Marc/Loui')) >>> marc.descendants (Node('/Udo/Marc/Lian'), Node('/Udo/Marc/Lian/Soe'), Node('/Udo/Marc/Loui')) >>> lian.descendants (Node('/Udo/Marc/Lian/Soe'),) """ return tuple(PreOrderIter(self))[1:] @property def root(self): """ Tree Root Node. >>> from anytree import Node >>> udo = Node("Udo") >>> marc = Node("Marc", parent=udo) >>> lian = Node("Lian", parent=marc) >>> udo.root Node('/Udo') >>> marc.root Node('/Udo') >>> lian.root Node('/Udo') """ node = self while node.parent is not None: node = node.parent return node @property def siblings(self): """ Tuple of nodes with the same parent. >>> from anytree import Node >>> udo = Node("Udo") >>> marc = Node("Marc", parent=udo) >>> lian = Node("Lian", parent=marc) >>> loui = Node("Loui", parent=marc) >>> lazy = Node("Lazy", parent=marc) >>> udo.siblings () >>> marc.siblings () >>> lian.siblings (Node('/Udo/Marc/Loui'), Node('/Udo/Marc/Lazy')) >>> loui.siblings (Node('/Udo/Marc/Lian'), Node('/Udo/Marc/Lazy')) """ parent = self.parent if parent is None: return tuple() return tuple(node for node in parent.children if node is not self) @property def leaves(self): """ Tuple of all leaf nodes. >>> from anytree import Node >>> udo = Node("Udo") >>> marc = Node("Marc", parent=udo) >>> lian = Node("Lian", parent=marc) >>> loui = Node("Loui", parent=marc) >>> lazy = Node("Lazy", parent=marc) >>> udo.leaves (Node('/Udo/Marc/Lian'), Node('/Udo/Marc/Loui'), Node('/Udo/Marc/Lazy')) >>> marc.leaves (Node('/Udo/Marc/Lian'), Node('/Udo/Marc/Loui'), Node('/Udo/Marc/Lazy')) """ return tuple(PreOrderIter(self, filter_=lambda node: node.is_leaf)) @property def is_leaf(self): """ `Node` has no children (External Node). >>> from anytree import Node >>> udo = Node("Udo") >>> marc = Node("Marc", parent=udo) >>> lian = Node("Lian", parent=marc) >>> udo.is_leaf False >>> marc.is_leaf False >>> lian.is_leaf True """ return len(self.__children_or_empty) == 0 @property def is_root(self): """ `Node` is tree root. >>> from anytree import Node >>> udo = Node("Udo") >>> marc = Node("Marc", parent=udo) >>> lian = Node("Lian", parent=marc) >>> udo.is_root True >>> marc.is_root False >>> lian.is_root False """ return self.parent is None @property def height(self): """ Number of edges on the longest path to a leaf `Node`. >>> from anytree import Node >>> udo = Node("Udo") >>> marc = Node("Marc", parent=udo) >>> lian = Node("Lian", parent=marc) >>> udo.height 2 >>> marc.height 1 >>> lian.height 0 """ children = self.__children_or_empty if children: return max(child.height for child in children) + 1 return 0 @property def depth(self): """ Number of edges to the root `Node`. >>> from anytree import Node >>> udo = Node("Udo") >>> marc = Node("Marc", parent=udo) >>> lian = Node("Lian", parent=marc) >>> udo.depth 0 >>> marc.depth 1 >>> lian.depth 2 """ # count without storing the entire path # pylint: disable=W0631 for depth, _ in enumerate(self.iter_path_reverse()): continue return depth @property def size(self): """ Tree size --- the number of nodes in tree starting at this node. >>> from anytree import Node >>> udo = Node("Udo") >>> marc = Node("Marc", parent=udo) >>> lian = Node("Lian", parent=marc) >>> loui = Node("Loui", parent=marc) >>> soe = Node("Soe", parent=lian) >>> udo.size 5 >>> marc.size 4 >>> lian.size 2 >>> loui.size 1 """ # count without storing the entire path # pylint: disable=W0631 for size, _ in enumerate(PreOrderIter(self), 1): continue return size def _pre_detach(self, parent): """Method call before detaching from `parent`.""" def _post_detach(self, parent): """Method call after detaching from `parent`.""" def _pre_attach(self, parent): """Method call before attaching to `parent`.""" def _post_attach(self, parent): """Method call after attaching to `parent`.""" anytree-2.12.1/anytree/node/node.py000066400000000000000000000053121452550712300171440ustar00rootroot00000000000000# -*- coding: utf-8 -*- from .nodemixin import NodeMixin from .util import _repr class Node(NodeMixin): """ A simple tree node with a `name` and any `kwargs`. Args: name: A name or any other object this node can reference to as identifier. Keyword Args: parent: Reference to parent node. children: Iterable with child nodes. *: Any other given attribute is just stored as object attribute. Other than :any:`AnyNode` this class has at least the `name` attribute, to distinguish between different instances. The `parent` attribute refers the parent node: >>> from anytree import Node, RenderTree >>> root = Node("root") >>> s0 = Node("sub0", parent=root) >>> s0b = Node("sub0B", parent=s0, foo=4, bar=109) >>> s0a = Node("sub0A", parent=s0) >>> s1 = Node("sub1", parent=root) >>> s1a = Node("sub1A", parent=s1) >>> s1b = Node("sub1B", parent=s1, bar=8) >>> s1c = Node("sub1C", parent=s1) >>> s1ca = Node("sub1Ca", parent=s1c) >>> print(RenderTree(root)) Node('/root') ├── Node('/root/sub0') │ ├── Node('/root/sub0/sub0B', bar=109, foo=4) │ └── Node('/root/sub0/sub0A') └── Node('/root/sub1') ├── Node('/root/sub1/sub1A') ├── Node('/root/sub1/sub1B', bar=8) └── Node('/root/sub1/sub1C') └── Node('/root/sub1/sub1C/sub1Ca') The same tree can be constructed by using the `children` attribute: >>> root = Node("root", children=[ ... Node("sub0", children=[ ... Node("sub0B", bar=109, foo=4), ... Node("sub0A", children=None), ... ]), ... Node("sub1", children=[ ... Node("sub1A"), ... Node("sub1B", bar=8, children=[]), ... Node("sub1C", children=[ ... Node("sub1Ca"), ... ]), ... ]), ... ]) >>> print(RenderTree(root)) Node('/root') ├── Node('/root/sub0') │ ├── Node('/root/sub0/sub0B', bar=109, foo=4) │ └── Node('/root/sub0/sub0A') └── Node('/root/sub1') ├── Node('/root/sub1/sub1A') ├── Node('/root/sub1/sub1B', bar=8) └── Node('/root/sub1/sub1C') └── Node('/root/sub1/sub1C/sub1Ca') """ def __init__(self, name, parent=None, children=None, **kwargs): self.__dict__.update(kwargs) self.name = name self.parent = parent if children: self.children = children def __repr__(self): args = ["%r" % self.separator.join([""] + [str(node.name) for node in self.path])] return _repr(self, args=args, nameblacklist=["name"]) anytree-2.12.1/anytree/node/nodemixin.py000066400000000000000000000405031452550712300202120ustar00rootroot00000000000000# -*- coding: utf-8 -*- import warnings from anytree.iterators import PreOrderIter from ..config import ASSERTIONS from .exceptions import LoopError, TreeError from .lightnodemixin import LightNodeMixin class NodeMixin: """ The :any:`NodeMixin` class extends any Python class to a tree node. The only tree relevant information is the `parent` attribute. If `None` the :any:`NodeMixin` is root node. If set to another node, the :any:`NodeMixin` becomes the child of it. The `children` attribute can be used likewise. If `None` the :any:`NodeMixin` has no children. The `children` attribute can be set to any iterable of :any:`NodeMixin` instances. These instances become children of the node. >>> from anytree import NodeMixin, RenderTree >>> class MyBaseClass(object): # Just an example of a base class ... foo = 4 >>> class MyClass(MyBaseClass, NodeMixin): # Add Node feature ... def __init__(self, name, length, width, parent=None, children=None): ... super(MyClass, self).__init__() ... self.name = name ... self.length = length ... self.width = width ... self.parent = parent ... if children: ... self.children = children Construction via `parent`: >>> my0 = MyClass('my0', 0, 0) >>> my1 = MyClass('my1', 1, 0, parent=my0) >>> my2 = MyClass('my2', 0, 2, parent=my0) >>> for pre, _, node in RenderTree(my0): ... treestr = u"%s%s" % (pre, node.name) ... print(treestr.ljust(8), node.length, node.width) my0 0 0 ├── my1 1 0 └── my2 0 2 Construction via `children`: >>> my0 = MyClass('my0', 0, 0, children=[ ... MyClass('my1', 1, 0), ... MyClass('my2', 0, 2), ... ]) >>> for pre, _, node in RenderTree(my0): ... treestr = u"%s%s" % (pre, node.name) ... print(treestr.ljust(8), node.length, node.width) my0 0 0 ├── my1 1 0 └── my2 0 2 Both approaches can be mixed: >>> my0 = MyClass('my0', 0, 0, children=[ ... MyClass('my1', 1, 0), ... ]) >>> my2 = MyClass('my2', 0, 2, parent=my0) >>> for pre, _, node in RenderTree(my0): ... treestr = u"%s%s" % (pre, node.name) ... print(treestr.ljust(8), node.length, node.width) my0 0 0 ├── my1 1 0 └── my2 0 2 """ separator = "/" @property def parent(self): """ Parent Node. On set, the node is detached from any previous parent node and attached to the new node. >>> from anytree import Node, RenderTree >>> udo = Node("Udo") >>> marc = Node("Marc") >>> lian = Node("Lian", parent=marc) >>> print(RenderTree(udo)) Node('/Udo') >>> print(RenderTree(marc)) Node('/Marc') └── Node('/Marc/Lian') **Attach** >>> marc.parent = udo >>> print(RenderTree(udo)) Node('/Udo') └── Node('/Udo/Marc') └── Node('/Udo/Marc/Lian') **Detach** To make a node to a root node, just set this attribute to `None`. >>> marc.is_root False >>> marc.parent = None >>> marc.is_root True """ if hasattr(self, "_NodeMixin__parent"): return self.__parent return None @parent.setter def parent(self, value): if value is not None and not isinstance(value, (NodeMixin, LightNodeMixin)): msg = "Parent node %r is not of type 'NodeMixin'." % (value,) raise TreeError(msg) if hasattr(self, "_NodeMixin__parent"): parent = self.__parent else: parent = None if parent is not value: self.__check_loop(value) self.__detach(parent) self.__attach(value) def __check_loop(self, node): if node is not None: if node is self: msg = "Cannot set parent. %r cannot be parent of itself." raise LoopError(msg % (self,)) if any(child is self for child in node.iter_path_reverse()): msg = "Cannot set parent. %r is parent of %r." raise LoopError(msg % (self, node)) def __detach(self, parent): # pylint: disable=W0212,W0238 if parent is not None: self._pre_detach(parent) parentchildren = parent.__children_or_empty if ASSERTIONS: # pragma: no branch assert any(child is self for child in parentchildren), "Tree is corrupt." # pragma: no cover # ATOMIC START parent.__children = [child for child in parentchildren if child is not self] self.__parent = None # ATOMIC END self._post_detach(parent) def __attach(self, parent): # pylint: disable=W0212 if parent is not None: self._pre_attach(parent) parentchildren = parent.__children_or_empty if ASSERTIONS: # pragma: no branch assert not any(child is self for child in parentchildren), "Tree is corrupt." # pragma: no cover # ATOMIC START parentchildren.append(self) self.__parent = parent # ATOMIC END self._post_attach(parent) @property def __children_or_empty(self): if not hasattr(self, "_NodeMixin__children"): self.__children = [] return self.__children @property def children(self): """ All child nodes. >>> from anytree import Node >>> n = Node("n") >>> a = Node("a", parent=n) >>> b = Node("b", parent=n) >>> c = Node("c", parent=n) >>> n.children (Node('/n/a'), Node('/n/b'), Node('/n/c')) Modifying the children attribute modifies the tree. **Detach** The children attribute can be updated by setting to an iterable. >>> n.children = [a, b] >>> n.children (Node('/n/a'), Node('/n/b')) Node `c` is removed from the tree. In case of an existing reference, the node `c` does not vanish and is the root of its own tree. >>> c Node('/c') **Attach** >>> d = Node("d") >>> d Node('/d') >>> n.children = [a, b, d] >>> n.children (Node('/n/a'), Node('/n/b'), Node('/n/d')) >>> d Node('/n/d') **Duplicate** A node can just be the children once. Duplicates cause a :any:`TreeError`: >>> n.children = [a, b, d, a] Traceback (most recent call last): ... anytree.node.exceptions.TreeError: Cannot add node Node('/n/a') multiple times as child. """ return tuple(self.__children_or_empty) @staticmethod def __check_children(children): seen = set() for child in children: if not isinstance(child, (NodeMixin, LightNodeMixin)): msg = "Cannot add non-node object %r. It is not a subclass of 'NodeMixin'." % (child,) raise TreeError(msg) childid = id(child) if childid not in seen: seen.add(childid) else: msg = "Cannot add node %r multiple times as child." % (child,) raise TreeError(msg) @children.setter def children(self, children): # convert iterable to tuple children = tuple(children) NodeMixin.__check_children(children) # ATOMIC start old_children = self.children del self.children try: self._pre_attach_children(children) for child in children: child.parent = self self._post_attach_children(children) if ASSERTIONS: # pragma: no branch assert len(self.children) == len(children) except Exception: self.children = old_children raise # ATOMIC end @children.deleter def children(self): children = self.children self._pre_detach_children(children) for child in self.children: child.parent = None if ASSERTIONS: # pragma: no branch assert len(self.children) == 0 self._post_detach_children(children) def _pre_detach_children(self, children): """Method call before detaching `children`.""" def _post_detach_children(self, children): """Method call after detaching `children`.""" def _pre_attach_children(self, children): """Method call before attaching `children`.""" def _post_attach_children(self, children): """Method call after attaching `children`.""" @property def path(self): """ Path from root node down to this `Node`. >>> from anytree import Node >>> udo = Node("Udo") >>> marc = Node("Marc", parent=udo) >>> lian = Node("Lian", parent=marc) >>> udo.path (Node('/Udo'),) >>> marc.path (Node('/Udo'), Node('/Udo/Marc')) >>> lian.path (Node('/Udo'), Node('/Udo/Marc'), Node('/Udo/Marc/Lian')) """ return self._path def iter_path_reverse(self): """ Iterate up the tree from the current node to the root node. >>> from anytree import Node >>> udo = Node("Udo") >>> marc = Node("Marc", parent=udo) >>> lian = Node("Lian", parent=marc) >>> for node in udo.iter_path_reverse(): ... print(node) Node('/Udo') >>> for node in marc.iter_path_reverse(): ... print(node) Node('/Udo/Marc') Node('/Udo') >>> for node in lian.iter_path_reverse(): ... print(node) Node('/Udo/Marc/Lian') Node('/Udo/Marc') Node('/Udo') """ node = self while node is not None: yield node node = node.parent @property def _path(self): return tuple(reversed(list(self.iter_path_reverse()))) @property def ancestors(self): """ All parent nodes and their parent nodes. >>> from anytree import Node >>> udo = Node("Udo") >>> marc = Node("Marc", parent=udo) >>> lian = Node("Lian", parent=marc) >>> udo.ancestors () >>> marc.ancestors (Node('/Udo'),) >>> lian.ancestors (Node('/Udo'), Node('/Udo/Marc')) """ if self.parent is None: return tuple() return self.parent.path @property def anchestors(self): """ All parent nodes and their parent nodes - see :any:`ancestors`. The attribute `anchestors` is just a typo of `ancestors`. Please use `ancestors`. This attribute will be removed in the 3.0.0 release. """ warnings.warn(".anchestors was a typo and will be removed in version 3.0.0", DeprecationWarning) return self.ancestors @property def descendants(self): """ All child nodes and all their child nodes. >>> from anytree import Node >>> udo = Node("Udo") >>> marc = Node("Marc", parent=udo) >>> lian = Node("Lian", parent=marc) >>> loui = Node("Loui", parent=marc) >>> soe = Node("Soe", parent=lian) >>> udo.descendants (Node('/Udo/Marc'), Node('/Udo/Marc/Lian'), Node('/Udo/Marc/Lian/Soe'), Node('/Udo/Marc/Loui')) >>> marc.descendants (Node('/Udo/Marc/Lian'), Node('/Udo/Marc/Lian/Soe'), Node('/Udo/Marc/Loui')) >>> lian.descendants (Node('/Udo/Marc/Lian/Soe'),) """ return tuple(PreOrderIter(self))[1:] @property def root(self): """ Tree Root Node. >>> from anytree import Node >>> udo = Node("Udo") >>> marc = Node("Marc", parent=udo) >>> lian = Node("Lian", parent=marc) >>> udo.root Node('/Udo') >>> marc.root Node('/Udo') >>> lian.root Node('/Udo') """ node = self while node.parent is not None: node = node.parent return node @property def siblings(self): """ Tuple of nodes with the same parent. >>> from anytree import Node >>> udo = Node("Udo") >>> marc = Node("Marc", parent=udo) >>> lian = Node("Lian", parent=marc) >>> loui = Node("Loui", parent=marc) >>> lazy = Node("Lazy", parent=marc) >>> udo.siblings () >>> marc.siblings () >>> lian.siblings (Node('/Udo/Marc/Loui'), Node('/Udo/Marc/Lazy')) >>> loui.siblings (Node('/Udo/Marc/Lian'), Node('/Udo/Marc/Lazy')) """ parent = self.parent if parent is None: return tuple() return tuple(node for node in parent.children if node is not self) @property def leaves(self): """ Tuple of all leaf nodes. >>> from anytree import Node >>> udo = Node("Udo") >>> marc = Node("Marc", parent=udo) >>> lian = Node("Lian", parent=marc) >>> loui = Node("Loui", parent=marc) >>> lazy = Node("Lazy", parent=marc) >>> udo.leaves (Node('/Udo/Marc/Lian'), Node('/Udo/Marc/Loui'), Node('/Udo/Marc/Lazy')) >>> marc.leaves (Node('/Udo/Marc/Lian'), Node('/Udo/Marc/Loui'), Node('/Udo/Marc/Lazy')) """ return tuple(PreOrderIter(self, filter_=lambda node: node.is_leaf)) @property def is_leaf(self): """ `Node` has no children (External Node). >>> from anytree import Node >>> udo = Node("Udo") >>> marc = Node("Marc", parent=udo) >>> lian = Node("Lian", parent=marc) >>> udo.is_leaf False >>> marc.is_leaf False >>> lian.is_leaf True """ return len(self.__children_or_empty) == 0 @property def is_root(self): """ `Node` is tree root. >>> from anytree import Node >>> udo = Node("Udo") >>> marc = Node("Marc", parent=udo) >>> lian = Node("Lian", parent=marc) >>> udo.is_root True >>> marc.is_root False >>> lian.is_root False """ return self.parent is None @property def height(self): """ Number of edges on the longest path to a leaf `Node`. >>> from anytree import Node >>> udo = Node("Udo") >>> marc = Node("Marc", parent=udo) >>> lian = Node("Lian", parent=marc) >>> udo.height 2 >>> marc.height 1 >>> lian.height 0 """ children = self.__children_or_empty if children: return max(child.height for child in children) + 1 return 0 @property def depth(self): """ Number of edges to the root `Node`. >>> from anytree import Node >>> udo = Node("Udo") >>> marc = Node("Marc", parent=udo) >>> lian = Node("Lian", parent=marc) >>> udo.depth 0 >>> marc.depth 1 >>> lian.depth 2 """ # count without storing the entire path # pylint: disable=W0631 for depth, _ in enumerate(self.iter_path_reverse()): continue return depth @property def size(self): """ Tree size --- the number of nodes in tree starting at this node. >>> from anytree import Node >>> udo = Node("Udo") >>> marc = Node("Marc", parent=udo) >>> lian = Node("Lian", parent=marc) >>> loui = Node("Loui", parent=marc) >>> soe = Node("Soe", parent=lian) >>> udo.size 5 >>> marc.size 4 >>> lian.size 2 >>> loui.size 1 """ # count without storing the entire path # pylint: disable=W0631 for size, _ in enumerate(PreOrderIter(self), 1): continue return size def _pre_detach(self, parent): """Method call before detaching from `parent`.""" def _post_detach(self, parent): """Method call after detaching from `parent`.""" def _pre_attach(self, parent): """Method call before attaching to `parent`.""" def _post_attach(self, parent): """Method call after attaching to `parent`.""" anytree-2.12.1/anytree/node/symlinknode.py000066400000000000000000000030651452550712300205560ustar00rootroot00000000000000# -*- coding: utf-8 -*- from .symlinknodemixin import SymlinkNodeMixin from .util import _repr class SymlinkNode(SymlinkNodeMixin): """ Tree node which references to another tree node. Args: target: Symbolic Link Target. Another tree node, which is refered to. Keyword Args: parent: Reference to parent node. children: Iterable with child nodes. *: Any other given attribute is just stored as attribute **in** `target`. The :any:`SymlinkNode` has its own parent and its own child nodes. All other attribute accesses are just forwarded to the target node. >>> from anytree import SymlinkNode, Node, RenderTree >>> root = Node("root") >>> s1 = Node("sub1", parent=root, bar=17) >>> l = SymlinkNode(s1, parent=root, baz=18) >>> l0 = Node("l0", parent=l) >>> print(RenderTree(root)) Node('/root') ├── Node('/root/sub1', bar=17, baz=18) └── SymlinkNode(Node('/root/sub1', bar=17, baz=18)) └── Node('/root/sub1/l0') Any modifications on the target node are also available on the linked node and vice-versa: >>> s1.foo = 4 >>> s1.foo 4 >>> l.foo 4 >>> l.foo = 9 >>> s1.foo 9 >>> l.foo 9 """ def __init__(self, target, parent=None, children=None, **kwargs): self.target = target self.target.__dict__.update(kwargs) self.parent = parent if children: self.children = children def __repr__(self): return _repr(self, [repr(self.target)], nameblacklist=("target",)) anytree-2.12.1/anytree/node/symlinknodemixin.py000066400000000000000000000037531452550712300216270ustar00rootroot00000000000000# -*- coding: utf-8 -*- from .nodemixin import NodeMixin class SymlinkNodeMixin(NodeMixin): """ The :any:`SymlinkNodeMixin` class extends any Python class to a symbolic link to a tree node. The class **MUST** have a `target` attribute refering to another tree node. The :any:`SymlinkNodeMixin` class has its own parent and its own child nodes. All other attribute accesses are just forwarded to the target node. A minimal implementation looks like (see :any:`SymlinkNode` for a full implemenation): >>> from anytree import SymlinkNodeMixin, Node, RenderTree >>> class SymlinkNode(SymlinkNodeMixin): ... def __init__(self, target, parent=None, children=None): ... self.target = target ... self.parent = parent ... if children: ... self.children = children ... def __repr__(self): ... return "SymlinkNode(%r)" % (self.target) >>> root = Node("root") >>> s1 = Node("sub1", parent=root) >>> l = SymlinkNode(s1, parent=root) >>> l0 = Node("l0", parent=l) >>> print(RenderTree(root)) Node('/root') ├── Node('/root/sub1') └── SymlinkNode(Node('/root/sub1')) └── Node('/root/sub1/l0') Any modifications on the target node are also available on the linked node and vice-versa: >>> s1.foo = 4 >>> s1.foo 4 >>> l.foo 4 >>> l.foo = 9 >>> s1.foo 9 >>> l.foo 9 """ def __getattr__(self, name): if name in ("_NodeMixin__parent", "_NodeMixin__children"): return super(SymlinkNodeMixin, self).__getattr__(name) if name == "__setstate__": raise AttributeError(name) return getattr(self.target, name) def __setattr__(self, name, value): if name in ("_NodeMixin__parent", "_NodeMixin__children", "parent", "children", "target"): super(SymlinkNodeMixin, self).__setattr__(name, value) else: setattr(self.target, name, value) anytree-2.12.1/anytree/node/util.py000066400000000000000000000006561452550712300172020ustar00rootroot00000000000000def _repr(node, args=None, nameblacklist=None): classname = node.__class__.__name__ args = args or [] nameblacklist = nameblacklist or [] for key, value in filter( lambda item: not item[0].startswith("_") and item[0] not in nameblacklist, sorted(node.__dict__.items(), key=lambda item: item[0]), ): args.append("%s=%r" % (key, value)) return "%s(%s)" % (classname, ", ".join(args)) anytree-2.12.1/anytree/render.py000066400000000000000000000243641452550712300165610ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ Tree Rendering. * :any:`RenderTree` using the following styles: * :any:`AsciiStyle` * :any:`ContStyle` * :any:`ContRoundStyle` * :any:`DoubleStyle` """ import collections import six from .config import ASSERTIONS Row = collections.namedtuple("Row", ("pre", "fill", "node")) class AbstractStyle: """ Tree Render Style. Args: vertical: Sign for vertical line. cont: Chars for a continued branch. end: Chars for the last branch. """ def __init__(self, vertical, cont, end): super(AbstractStyle, self).__init__() self.vertical = vertical self.cont = cont self.end = end if ASSERTIONS: # pragma: no branch assert len(cont) == len(vertical) == len(end), "'%s', '%s' and '%s' need to have equal length" % ( vertical, cont, end, ) @property def empty(self): """Empty string as placeholder.""" return " " * len(self.end) def __repr__(self): classname = self.__class__.__name__ return "%s()" % classname class AsciiStyle(AbstractStyle): """ Ascii style. >>> from anytree import Node, RenderTree >>> root = Node("root") >>> s0 = Node("sub0", parent=root) >>> s0b = Node("sub0B", parent=s0) >>> s0a = Node("sub0A", parent=s0) >>> s1 = Node("sub1", parent=root) >>> print(RenderTree(root, style=AsciiStyle())) Node('/root') |-- Node('/root/sub0') | |-- Node('/root/sub0/sub0B') | +-- Node('/root/sub0/sub0A') +-- Node('/root/sub1') """ def __init__(self): super(AsciiStyle, self).__init__("| ", "|-- ", "+-- ") class ContStyle(AbstractStyle): """ Continued style, without gaps. >>> from anytree import Node, RenderTree >>> root = Node("root") >>> s0 = Node("sub0", parent=root) >>> s0b = Node("sub0B", parent=s0) >>> s0a = Node("sub0A", parent=s0) >>> s1 = Node("sub1", parent=root) >>> print(RenderTree(root, style=ContStyle())) Node('/root') ├── Node('/root/sub0') │ ├── Node('/root/sub0/sub0B') │ └── Node('/root/sub0/sub0A') └── Node('/root/sub1') """ def __init__(self): super(ContStyle, self).__init__("\u2502 ", "\u251c\u2500\u2500 ", "\u2514\u2500\u2500 ") class ContRoundStyle(AbstractStyle): """ Continued style, without gaps, round edges. >>> from anytree import Node, RenderTree >>> root = Node("root") >>> s0 = Node("sub0", parent=root) >>> s0b = Node("sub0B", parent=s0) >>> s0a = Node("sub0A", parent=s0) >>> s1 = Node("sub1", parent=root) >>> print(RenderTree(root, style=ContRoundStyle())) Node('/root') ├── Node('/root/sub0') │ ├── Node('/root/sub0/sub0B') │ ╰── Node('/root/sub0/sub0A') ╰── Node('/root/sub1') """ def __init__(self): super(ContRoundStyle, self).__init__("\u2502 ", "\u251c\u2500\u2500 ", "\u2570\u2500\u2500 ") class DoubleStyle(AbstractStyle): """ Double line style, without gaps. >>> from anytree import Node, RenderTree >>> root = Node("root") >>> s0 = Node("sub0", parent=root) >>> s0b = Node("sub0B", parent=s0) >>> s0a = Node("sub0A", parent=s0) >>> s1 = Node("sub1", parent=root) >>> print(RenderTree(root, style=DoubleStyle)) Node('/root') ╠══ Node('/root/sub0') ║ ╠══ Node('/root/sub0/sub0B') ║ ╚══ Node('/root/sub0/sub0A') ╚══ Node('/root/sub1') """ def __init__(self): super(DoubleStyle, self).__init__("\u2551 ", "\u2560\u2550\u2550 ", "\u255a\u2550\u2550 ") @six.python_2_unicode_compatible class RenderTree: """ Render tree starting at `node`. Keyword Args: style (AbstractStyle): Render Style. childiter: Child iterator. maxlevel: Limit rendering to this depth. :any:`RenderTree` is an iterator, returning a tuple with 3 items: `pre` tree prefix. `fill` filling for multiline entries. `node` :any:`NodeMixin` object. It is up to the user to assemble these parts to a whole. >>> from anytree import Node, RenderTree >>> root = Node("root", lines=["c0fe", "c0de"]) >>> s0 = Node("sub0", parent=root, lines=["ha", "ba"]) >>> s0b = Node("sub0B", parent=s0, lines=["1", "2", "3"]) >>> s0a = Node("sub0A", parent=s0, lines=["a", "b"]) >>> s1 = Node("sub1", parent=root, lines=["Z"]) Simple one line: >>> for pre, _, node in RenderTree(root): ... print("%s%s" % (pre, node.name)) root ├── sub0 │ ├── sub0B │ └── sub0A └── sub1 Multiline: >>> for pre, fill, node in RenderTree(root): ... print("%s%s" % (pre, node.lines[0])) ... for line in node.lines[1:]: ... print("%s%s" % (fill, line)) c0fe c0de ├── ha │ ba │ ├── 1 │ │ 2 │ │ 3 │ └── a │ b └── Z `maxlevel` limits the depth of the tree: >>> print(RenderTree(root, maxlevel=2)) Node('/root', lines=['c0fe', 'c0de']) ├── Node('/root/sub0', lines=['ha', 'ba']) └── Node('/root/sub1', lines=['Z']) The `childiter` is responsible for iterating over child nodes at the same level. An reversed order can be achived by using `reversed`. >>> for row in RenderTree(root, childiter=reversed): ... print("%s%s" % (row.pre, row.node.name)) root ├── sub1 └── sub0 ├── sub0A └── sub0B Or writing your own sort function: >>> def mysort(items): ... return sorted(items, key=lambda item: item.name) >>> for row in RenderTree(root, childiter=mysort): ... print("%s%s" % (row.pre, row.node.name)) root ├── sub0 │ ├── sub0A │ └── sub0B └── sub1 :any:`by_attr` simplifies attribute rendering and supports multiline: >>> print(RenderTree(root).by_attr()) root ├── sub0 │ ├── sub0B │ └── sub0A └── sub1 >>> print(RenderTree(root).by_attr("lines")) c0fe c0de ├── ha │ ba │ ├── 1 │ │ 2 │ │ 3 │ └── a │ b └── Z And can be a function: >>> print(RenderTree(root).by_attr(lambda n: " ".join(n.lines))) c0fe c0de ├── ha ba │ ├── 1 2 3 │ └── a b └── Z """ def __init__(self, node, style=ContStyle(), childiter=list, maxlevel=None): if not isinstance(style, AbstractStyle): style = style() self.node = node self.style = style self.childiter = childiter self.maxlevel = maxlevel def __iter__(self): return self.__next(self.node, tuple()) def __next(self, node, continues, level=0): yield RenderTree.__item(node, continues, self.style) level += 1 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): if not continues: return Row("", "", node) items = [style.vertical if cont else style.empty for cont in continues] indent = "".join(items[:-1]) branch = style.cont if continues[-1] else style.end pre = indent + branch fill = "".join(items) return Row(pre, fill, node) def __str__(self): 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__ args = [repr(self.node), "style=%s" % repr(self.style), "childiter=%s" % repr(self.childiter)] return "%s(%s)" % (classname, ", ".join(args)) def by_attr(self, attrname="name"): """ Return rendered tree with node attribute `attrname`. >>> from anytree import AnyNode, RenderTree >>> root = AnyNode(id="root") >>> s0 = AnyNode(id="sub0", parent=root) >>> s0b = AnyNode(id="sub0B", parent=s0, foo=4, bar=109) >>> s0a = AnyNode(id="sub0A", parent=s0) >>> s1 = AnyNode(id="sub1", parent=root) >>> s1a = AnyNode(id="sub1A", parent=s1) >>> s1b = AnyNode(id="sub1B", parent=s1, bar=8) >>> s1c = AnyNode(id="sub1C", parent=s1) >>> s1ca = AnyNode(id="sub1Ca", parent=s1c) >>> print(RenderTree(root).by_attr('id')) root ├── sub0 │ ├── sub0B │ └── sub0A └── sub1 ├── sub1A ├── sub1B └── sub1C └── sub1Ca """ def get(): 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: nextitem = next(iter_) except StopIteration: pass else: item = nextitem while True: try: nextitem = next(iter_) yield item, False except StopIteration: yield nextitem, True break item = nextitem anytree-2.12.1/anytree/resolver.py000066400000000000000000000266171452550712300171460ustar00rootroot00000000000000# -*- coding: utf-8 -*- 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) # resolve root 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: # pragma: no branch assert node is not None 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 = [] 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)) anytree-2.12.1/anytree/search.py000066400000000000000000000162261452550712300165450ustar00rootroot00000000000000""" Node Searching. .. note:: You can speed-up node searching, by installing https://pypi.org/project/fastcache/ and using :any:`cachedsearch`. """ from anytree.iterators import PreOrderIter def findall(node, filter_=None, stop=None, maxlevel=None, mincount=None, maxcount=None): """ Search nodes matching `filter_` but stop at `maxlevel` or `stop`. Return tuple with matching nodes. Args: node: top node, start searching. Keyword Args: filter_: function called with every `node` as argument, `node` is returned if `True`. stop: stop iteration at `node` if `stop` function returns `True` for `node`. maxlevel (int): maximum descending in the node hierarchy. mincount (int): minimum number of nodes. maxcount (int): maximum number of nodes. Example tree: >>> from anytree import Node, RenderTree, AsciiStyle >>> f = Node("f") >>> b = Node("b", parent=f) >>> a = Node("a", parent=b) >>> d = Node("d", parent=b) >>> c = Node("c", parent=d) >>> e = Node("e", parent=d) >>> g = Node("g", parent=f) >>> i = Node("i", parent=g) >>> h = Node("h", parent=i) >>> print(RenderTree(f, style=AsciiStyle()).by_attr()) f |-- b | |-- a | +-- d | |-- c | +-- e +-- g +-- i +-- h >>> findall(f, filter_=lambda node: node.name in ("a", "b")) (Node('/f/b'), Node('/f/b/a')) >>> findall(f, filter_=lambda node: d in node.path) (Node('/f/b/d'), Node('/f/b/d/c'), Node('/f/b/d/e')) The number of matches can be limited: >>> findall(f, filter_=lambda node: d in node.path, mincount=4) # doctest: +ELLIPSIS Traceback (most recent call last): ... anytree.search.CountError: Expecting at least 4 elements, but found 3. ... Node('/f/b/d/e')) >>> findall(f, filter_=lambda node: d in node.path, maxcount=2) # doctest: +ELLIPSIS Traceback (most recent call last): ... anytree.search.CountError: Expecting 2 elements at maximum, but found 3. ... Node('/f/b/d/e')) """ return _findall(node, filter_=filter_, stop=stop, maxlevel=maxlevel, mincount=mincount, maxcount=maxcount) def findall_by_attr(node, value, name="name", maxlevel=None, mincount=None, maxcount=None): """ Search nodes with attribute `name` having `value` but stop at `maxlevel`. Return tuple with matching nodes. Args: node: top node, start searching. value: value which need to match Keyword Args: name (str): attribute name need to match maxlevel (int): maximum descending in the node hierarchy. mincount (int): minimum number of nodes. maxcount (int): maximum number of nodes. Example tree: >>> from anytree import Node, RenderTree, AsciiStyle >>> f = Node("f") >>> b = Node("b", parent=f) >>> a = Node("a", parent=b) >>> d = Node("d", parent=b) >>> c = Node("c", parent=d) >>> e = Node("e", parent=d) >>> g = Node("g", parent=f) >>> i = Node("i", parent=g) >>> h = Node("h", parent=i) >>> print(RenderTree(f, style=AsciiStyle()).by_attr()) f |-- b | |-- a | +-- d | |-- c | +-- e +-- g +-- i +-- h >>> findall_by_attr(f, "d") (Node('/f/b/d'),) """ return _findall( node, filter_=lambda n: _filter_by_name(n, name, value), maxlevel=maxlevel, mincount=mincount, maxcount=maxcount, ) def find(node, filter_=None, stop=None, maxlevel=None): """ Search for *single* node matching `filter_` but stop at `maxlevel` or `stop`. Return matching node. Args: node: top node, start searching. Keyword Args: filter_: function called with every `node` as argument, `node` is returned if `True`. stop: stop iteration at `node` if `stop` function returns `True` for `node`. maxlevel (int): maximum descending in the node hierarchy. Example tree: >>> from anytree import Node, RenderTree, AsciiStyle >>> f = Node("f") >>> b = Node("b", parent=f) >>> a = Node("a", parent=b) >>> d = Node("d", parent=b) >>> c = Node("c", parent=d) >>> e = Node("e", parent=d) >>> g = Node("g", parent=f) >>> i = Node("i", parent=g) >>> h = Node("h", parent=i) >>> print(RenderTree(f, style=AsciiStyle()).by_attr()) f |-- b | |-- a | +-- d | |-- c | +-- e +-- g +-- i +-- h >>> find(f, lambda node: node.name == "d") Node('/f/b/d') >>> find(f, lambda node: node.name == "z") >>> find(f, lambda node: b in node.path) # doctest: +ELLIPSIS Traceback (most recent call last): ... anytree.search.CountError: Expecting 1 elements at maximum, but found 5. (Node('/f/b')... Node('/f/b/d/e')) """ return _find(node, filter_=filter_, stop=stop, maxlevel=maxlevel) def find_by_attr(node, value, name="name", maxlevel=None): """ Search for *single* node with attribute `name` having `value` but stop at `maxlevel`. Return matching node. Args: node: top node, start searching. value: value which need to match Keyword Args: name (str): attribute name need to match maxlevel (int): maximum descending in the node hierarchy. Example tree: >>> from anytree import Node, RenderTree, AsciiStyle >>> f = Node("f") >>> b = Node("b", parent=f) >>> a = Node("a", parent=b) >>> d = Node("d", parent=b) >>> c = Node("c", parent=d, foo=4) >>> e = Node("e", parent=d) >>> g = Node("g", parent=f) >>> i = Node("i", parent=g) >>> h = Node("h", parent=i) >>> print(RenderTree(f, style=AsciiStyle()).by_attr()) f |-- b | |-- a | +-- d | |-- c | +-- e +-- g +-- i +-- h >>> find_by_attr(f, "d") Node('/f/b/d') >>> find_by_attr(f, name="foo", value=4) Node('/f/b/d/c', foo=4) >>> find_by_attr(f, name="foo", value=8) """ return _find(node, filter_=lambda n: _filter_by_name(n, name, value), maxlevel=maxlevel) def _find(node, filter_, stop=None, maxlevel=None): items = _findall(node, filter_, stop=stop, maxlevel=maxlevel, maxcount=1) return items[0] if items else None def _findall(node, filter_, stop=None, maxlevel=None, mincount=None, maxcount=None): result = tuple(PreOrderIter(node, filter_, stop, maxlevel)) resultlen = len(result) if mincount is not None and resultlen < mincount: msg = "Expecting at least %d elements, but found %d." raise CountError(msg % (mincount, resultlen), result) if maxcount is not None and resultlen > maxcount: msg = "Expecting %d elements at maximum, but found %d." raise CountError(msg % (maxcount, resultlen), result) return result def _filter_by_name(node, name, value): try: return getattr(node, name) == value except AttributeError: return False class CountError(RuntimeError): def __init__(self, msg, result): """Error raised on `mincount` or `maxcount` mismatch.""" if result: msg += " " + repr(result) super(CountError, self).__init__(msg) anytree-2.12.1/anytree/util/000077500000000000000000000000001452550712300156745ustar00rootroot00000000000000anytree-2.12.1/anytree/util/__init__.py000066400000000000000000000043461452550712300200140ustar00rootroot00000000000000"""Utilities.""" def commonancestors(*nodes): """ Determine common ancestors of `nodes`. >>> from anytree import Node, util >>> udo = Node("Udo") >>> marc = Node("Marc", parent=udo) >>> lian = Node("Lian", parent=marc) >>> dan = Node("Dan", parent=udo) >>> jet = Node("Jet", parent=dan) >>> jan = Node("Jan", parent=dan) >>> joe = Node("Joe", parent=dan) >>> util.commonancestors(jet, joe) (Node('/Udo'), Node('/Udo/Dan')) >>> util.commonancestors(jet, marc) (Node('/Udo'),) >>> util.commonancestors(jet) (Node('/Udo'), Node('/Udo/Dan')) >>> util.commonancestors() () """ ancestors = [node.ancestors for node in nodes] common = [] for parentnodes in zip(*ancestors): parentnode = parentnodes[0] if all(parentnode is p for p in parentnodes[1:]): common.append(parentnode) else: break return tuple(common) def leftsibling(node): """ Return Left Sibling of `node`. >>> from anytree import Node, util >>> dan = Node("Dan") >>> jet = Node("Jet", parent=dan) >>> jan = Node("Jan", parent=dan) >>> joe = Node("Joe", parent=dan) >>> print(util.leftsibling(dan)) None >>> print(util.leftsibling(jet)) None >>> print(util.leftsibling(jan)) Node('/Dan/Jet') >>> print(util.leftsibling(joe)) Node('/Dan/Jan') """ if node.parent: pchildren = node.parent.children idx = pchildren.index(node) if idx: return pchildren[idx - 1] return None def rightsibling(node): """ Return Right Sibling of `node`. >>> from anytree import Node, util >>> dan = Node("Dan") >>> jet = Node("Jet", parent=dan) >>> jan = Node("Jan", parent=dan) >>> joe = Node("Joe", parent=dan) >>> print(util.rightsibling(dan)) None >>> print(util.rightsibling(jet)) Node('/Dan/Jan') >>> print(util.rightsibling(jan)) Node('/Dan/Joe') >>> print(util.rightsibling(joe)) None """ if node.parent: pchildren = node.parent.children idx = pchildren.index(node) try: return pchildren[idx + 1] except IndexError: return None else: return None anytree-2.12.1/anytree/walker.py000066400000000000000000000054621452550712300165650ustar00rootroot00000000000000# -*- coding: utf-8 -*- from .config import ASSERTIONS class Walker: """Walk from one node to another.""" @staticmethod def walk(start, end): """ Walk from `start` node to `end` node. Returns: (upwards, common, downwards): `upwards` is a list of nodes to go upward to. `common` top node. `downwards` is a list of nodes to go downward to. Raises: WalkError: on no common root node. Example: >>> from anytree import Node, RenderTree, AsciiStyle >>> f = Node("f") >>> b = Node("b", parent=f) >>> a = Node("a", parent=b) >>> d = Node("d", parent=b) >>> c = Node("c", parent=d) >>> e = Node("e", parent=d) >>> g = Node("g", parent=f) >>> i = Node("i", parent=g) >>> h = Node("h", parent=i) >>> print(RenderTree(f, style=AsciiStyle())) Node('/f') |-- Node('/f/b') | |-- Node('/f/b/a') | +-- Node('/f/b/d') | |-- Node('/f/b/d/c') | +-- Node('/f/b/d/e') +-- Node('/f/g') +-- Node('/f/g/i') +-- Node('/f/g/i/h') Create a walker: >>> w = Walker() This class is made for walking: >>> w.walk(f, f) ((), Node('/f'), ()) >>> w.walk(f, b) ((), Node('/f'), (Node('/f/b'),)) >>> w.walk(b, f) ((Node('/f/b'),), Node('/f'), ()) >>> w.walk(h, e) ((Node('/f/g/i/h'), Node('/f/g/i'), Node('/f/g')), Node('/f'), (Node('/f/b'), Node('/f/b/d'), Node('/f/b/d/e'))) >>> w.walk(d, e) ((), Node('/f/b/d'), (Node('/f/b/d/e'),)) For a proper walking the nodes need to be part of the same tree: >>> w.walk(Node("a"), Node("b")) Traceback (most recent call last): ... anytree.walker.WalkError: Node('/a') and Node('/b') are not part of the same tree. """ startpath = start.path endpath = end.path if start.root is not end.root: msg = "%r and %r are not part of the same tree." % (start, end) raise WalkError(msg) # common common = Walker.__calc_common(startpath, endpath) if ASSERTIONS: # pragma: no branch assert common[0] is start.root len_common = len(common) # upwards if start is common[-1]: upwards = tuple() else: upwards = tuple(reversed(startpath[len_common:])) # down if end is common[-1]: down = tuple() else: down = endpath[len_common:] return upwards, common[-1], down @staticmethod def __calc_common(start, end): return tuple(si for si, ei in zip(start, end) if si is ei) class WalkError(RuntimeError): """Walk Error.""" anytree-2.12.1/conftest.py000066400000000000000000000012321452550712300154450ustar00rootroot00000000000000import pytest # https://stackoverflow.com/questions/46962007/how-to-automatically-change-to-pytest-temporary-directory-for-all-doctests @pytest.fixture(autouse=True) def _docdir(request): # Trigger ONLY for the doctests. doctest_plugin = request.config.pluginmanager.getplugin("doctest") if isinstance(request.node, doctest_plugin.DoctestItem): # Get the fixture dynamically by its name. tmpdir = request.getfixturevalue("tmpdir") # Chdir only for the duration of the test. with tmpdir.as_cwd(): yield else: # For normal tests, we have to yield, since this is a yield-fixture. yield anytree-2.12.1/docs/000077500000000000000000000000001452550712300142005ustar00rootroot00000000000000anytree-2.12.1/docs/Makefile000066400000000000000000000011711452550712300156400ustar00rootroot00000000000000# Minimal makefile for Sphinx documentation # # You can set these variables from the command line, and also # from the environment for the first two. SPHINXOPTS ?= SPHINXBUILD ?= sphinx-build SOURCEDIR = . BUILDDIR = build # Put it first so that "make" without argument is like "make help". help: @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) .PHONY: help Makefile # Catch-all target: route all unknown targets to Sphinx using the new # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). %: Makefile @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) anytree-2.12.1/docs/api.rst000066400000000000000000000003471452550712300155070ustar00rootroot00000000000000API === .. toctree:: :maxdepth: 1 api/anytree.node api/anytree.iterators api/anytree.render api/anytree.search api/anytree.cachedsearch api/anytree.resolver api/anytree.walker api/anytree.util anytree-2.12.1/docs/api/000077500000000000000000000000001452550712300147515ustar00rootroot00000000000000anytree-2.12.1/docs/api/anytree.cachedsearch.rst000066400000000000000000000001101452550712300215360ustar00rootroot00000000000000Cached Searching ================ .. automodule:: anytree.cachedsearch anytree-2.12.1/docs/api/anytree.iterators.rst000066400000000000000000000004761452550712300211740ustar00rootroot00000000000000Tree Iteration ============== .. automodule:: anytree.iterators .. automodule:: anytree.iterators.preorderiter .. automodule:: anytree.iterators.postorderiter .. automodule:: anytree.iterators.levelorderiter .. automodule:: anytree.iterators.levelordergroupiter .. automodule:: anytree.iterators.zigzaggroupiter anytree-2.12.1/docs/api/anytree.node.rst000066400000000000000000000006041452550712300200760ustar00rootroot00000000000000Node Classes ============ .. automodule:: anytree.node .. automodule:: anytree.node.anynode .. automodule:: anytree.node.node .. automodule:: anytree.node.nodemixin :private-members: .. automodule:: anytree.node.lightnodemixin :private-members: .. automodule:: anytree.node.symlinknode .. automodule:: anytree.node.symlinknodemixin .. automodule:: anytree.node.exceptions anytree-2.12.1/docs/api/anytree.render.rst000066400000000000000000000000761452550712300204330ustar00rootroot00000000000000Tree Rendering ============== .. automodule:: anytree.render anytree-2.12.1/docs/api/anytree.resolver.rst000066400000000000000000000001021452550712300210030ustar00rootroot00000000000000Node Resolution =============== .. automodule:: anytree.resolver anytree-2.12.1/docs/api/anytree.search.rst000066400000000000000000000000641452550712300204160ustar00rootroot00000000000000Searching ========= .. automodule:: anytree.search anytree-2.12.1/docs/api/anytree.util.rst000066400000000000000000000000621452550712300201240ustar00rootroot00000000000000Utilities ========= .. automodule:: anytree.util anytree-2.12.1/docs/api/anytree.walker.rst000066400000000000000000000000721452550712300204350ustar00rootroot00000000000000Tree Walking ============ .. automodule:: anytree.walker anytree-2.12.1/docs/conf.py000066400000000000000000000025461452550712300155060ustar00rootroot00000000000000# Configuration file for the Sphinx documentation builder. # # For the full list of built-in configuration values, see the documentation: # https://www.sphinx-doc.org/en/master/usage/configuration.html # -- Project information ----------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information project = "anytree" copyright = "2016-2023, c0fec0de" author = "c0fec0de" description = "Python Tree Data" # -- General configuration --------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration extensions = [ "sphinx.ext.autodoc", "sphinx.ext.doctest", "sphinx.ext.viewcode", "sphinx.ext.napoleon", "sphinx.ext.intersphinx", ] templates_path = ["_templates"] exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] source_suffix = ".rst" # -- Options for HTML output ------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output html_theme = "sphinxdoc" html_static_path = ["static"] intersphinx_mapping = { "python": ("http://docs.python.org/3", None), } intersphinx_cache_limit = 1 autodoc_default_options = { "members": None, # Include all members (methods). "member-order": "bysource", } autoclass_content = "both" anytree-2.12.1/docs/dotexport.rst000066400000000000000000000014051452550712300167620ustar00rootroot00000000000000Export to DOT ============= Any :any:`anytree` graph can be converted to a graphviz_ graph. This tree:: >>> from anytree import Node >>> root = Node("root") >>> s0 = Node("sub0", parent=root) >>> s0b = Node("sub0B", parent=s0) >>> s0a = Node("sub0A", parent=s0) >>> s1 = Node("sub1", parent=root) >>> s1a = Node("sub1A", parent=s1) >>> s1b = Node("sub1B", parent=s1) >>> s1c = Node("sub1C", parent=s1) >>> s1ca = Node("sub1Ca", parent=s1c) Can be rendered to a tree by :any:`RenderTreeGraph`:: >>> from anytree.dotexport import RenderTreeGraph >>> RenderTreeGraph(root).to_picture("tree.png") .. image:: static/tree.png .. automodule:: anytree.dotexport :inherited-members: .. _graphviz: http://www.graphviz.org/ anytree-2.12.1/docs/exporter.rst000066400000000000000000000006721452550712300166070ustar00rootroot00000000000000Exporter ======== One fundamental idea behind *anytree* is the common tree node data structure, which can be imported from different formats and exported to different formats. Available exporters: .. toctree:: :maxdepth: 1 exporter/dictexporter exporter/jsonexporter exporter/dotexporter exporter/mermaidexporter Exporter missing? File a request here: Issues_. .. _Issues: https://github.com/c0fec0de/anytree/issues anytree-2.12.1/docs/exporter/000077500000000000000000000000001452550712300160505ustar00rootroot00000000000000anytree-2.12.1/docs/exporter/dictexporter.rst000066400000000000000000000001271452550712300213160ustar00rootroot00000000000000Dictionary Exporter =================== .. automodule:: anytree.exporter.dictexporter anytree-2.12.1/docs/exporter/dotexporter.rst000066400000000000000000000002451452550712300211620ustar00rootroot00000000000000Dot Exporter ============ For any details about the `dot` language, see graphviz_ .. automodule:: anytree.exporter.dotexporter .. _graphviz: https://graphviz.org anytree-2.12.1/docs/exporter/jsonexporter.rst000066400000000000000000000001131452550712300213370ustar00rootroot00000000000000JSON Exporter ============= .. automodule:: anytree.exporter.jsonexporter anytree-2.12.1/docs/exporter/mermaidexporter.rst000066400000000000000000000003131452550712300220060ustar00rootroot00000000000000Mermaid Exporter ================ For any details about the `Mermaid` language, see mermaid_ .. automodule:: anytree.exporter.mermaidexporter .. _mermaid: https://mermaid.js.org/syntax/flowchart.html anytree-2.12.1/docs/importer.rst000066400000000000000000000006041452550712300165730ustar00rootroot00000000000000Importer ======== One fundamental idea behind *anytree* is the common tree node data structure, which can be imported from different formats and exported to different formats. Available importers: .. toctree:: :maxdepth: 1 importer/dictimporter importer/jsonimporter Importer missing? File a request here: Issues_. .. _Issues: https://github.com/c0fec0de/anytree/issues anytree-2.12.1/docs/importer/000077500000000000000000000000001452550712300160415ustar00rootroot00000000000000anytree-2.12.1/docs/importer/dictimporter.rst000066400000000000000000000001271452550712300213000ustar00rootroot00000000000000Dictionary Importer =================== .. automodule:: anytree.importer.dictimporter anytree-2.12.1/docs/importer/jsonimporter.rst000066400000000000000000000001131452550712300213210ustar00rootroot00000000000000JSON Importer ============= .. automodule:: anytree.importer.jsonimporter anytree-2.12.1/docs/index.rst000066400000000000000000000156051452550712300160500ustar00rootroot00000000000000******************** Any Python Tree Data ******************** .. image:: https://badge.fury.io/py/anytree.svg :target: https://badge.fury.io/py/anytree .. image:: https://img.shields.io/pypi/dm/anytree.svg?label=pypi%20downloads :target: https://pypi.python.org/pypi/anytree .. image:: https://readthedocs.org/projects/anytree/badge/?version=latest :target: https://anytree.readthedocs.io/en/latest/?badge=latest .. image:: https://coveralls.io/repos/github/c0fec0de/anytree/badge.svg :target: https://coveralls.io/github/c0fec0de/anytree .. image:: https://readthedocs.org/projects/anytree/badge/?version=stable :target: https://anytree.readthedocs.io/en/stable/ .. image:: https://api.codeclimate.com/v1/badges/e6d325d6fd23a93aab20/maintainability :target: https://codeclimate.com/github/c0fec0de/anytree/maintainability :alt: Maintainability .. image:: https://img.shields.io/pypi/pyversions/anytree.svg :target: https://pypi.python.org/pypi/anytree .. image:: https://img.shields.io/badge/code%20style-pep8-brightgreen.svg :target: https://www.python.org/dev/peps/pep-0008/ .. image:: https://img.shields.io/badge/code%20style-pep257-brightgreen.svg :target: https://www.python.org/dev/peps/pep-0257/ .. image:: https://img.shields.io/badge/linter-pylint-%231674b1?style=flat :target: https://www.pylint.org/ .. image:: https://img.shields.io/badge/code%20style-black-000000.svg :target: https://github.com/psf/black .. image:: https://img.shields.io/github/contributors/c0fec0de/anytree.svg :target: https://github.com/c0fec0de/anytree/graphs/contributors/ .. image:: https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square :target: http://makeapullrequest.com .. image:: https://img.shields.io/github/issues-pr/c0fec0de/anytree.svg :target: https://github.com/c0fec0de/anytree/pulls .. image:: https://img.shields.io/github/issues-pr-closed/c0fec0de/anytree.svg :target: https://github.com/c0fec0de/anytree/pulls?q=is%3Apr+is%3Aclosed Simple, lightweight and extensible Tree_ data structure. Feel free to share_ info about your anytree project. .. _share: https://github.com/c0fec0de/anytree/issues/34 .. toctree:: :maxdepth: 2 installation intro api importer exporter tricks Links ===== * Documentation_ * GitHub_ * PyPI_ * Changelog_ * Issues_ * Contributors_ * If you enjoy anytree_ .. image:: https://cdn.buymeacoffee.com/buttons/default-orange.png :width: 150 :target: https://www.buymeacoffee.com/1oYX0sw Feel free to share_ info about your anytree project. .. _anytree: https://anytree.readthedocs.io/en/stable/ .. _Documentation: https://anytree.readthedocs.io/en/stable/ .. _GitHub: https://github.com/c0fec0de/anytree .. _PyPI: https://pypi.org/project/anytree/ .. _Changelog: https://github.com/c0fec0de/anytree/releases .. _Issues: https://github.com/c0fec0de/anytree/issues .. _Contributors: https://github.com/c0fec0de/anytree/graphs/contributors .. _share: https://github.com/c0fec0de/anytree/issues/34 .. _Tree: https://en.wikipedia.org/wiki/Tree_(data_structure) Getting started =============== .. _getting_started: Usage is simple. **Construction** >>> from anytree import Node, RenderTree >>> udo = Node("Udo") >>> marc = Node("Marc", parent=udo) >>> lian = Node("Lian", parent=marc) >>> dan = Node("Dan", parent=udo) >>> jet = Node("Jet", parent=dan) >>> jan = Node("Jan", parent=dan) >>> joe = Node("Joe", parent=dan) **Node** >>> print(udo) Node('/Udo') >>> print(joe) Node('/Udo/Dan/Joe') **Tree** >>> for pre, fill, node in RenderTree(udo): ... print("%s%s" % (pre, node.name)) Udo ├── Marc │ └── Lian └── Dan ├── Jet ├── Jan └── Joe For details see :any:`Node` and :any:`RenderTree`. **Visualization** >>> from anytree.exporter import UniqueDotExporter >>> # graphviz needs to be installed for the next line! >>> UniqueDotExporter(udo).to_picture("udo.png") .. image:: static/udo.png The :any:`UniqueDotExporter` can be started at any node and has various formatting hookups: >>> UniqueDotExporter(dan, ... nodeattrfunc=lambda node: "fixedsize=true, width=1, height=1, shape=diamond", ... edgeattrfunc=lambda parent, child: "style=bold" ... ).to_picture("dan.png") .. image:: static/dan.png **Manipulation** A second tree: >>> mary = Node("Mary") >>> urs = Node("Urs", parent=mary) >>> chris = Node("Chris", parent=mary) >>> marta = Node("Marta", parent=mary) >>> print(RenderTree(mary)) Node('/Mary') ├── Node('/Mary/Urs') ├── Node('/Mary/Chris') └── Node('/Mary/Marta') Append: >>> udo.parent = mary >>> print(RenderTree(mary)) Node('/Mary') ├── Node('/Mary/Urs') ├── Node('/Mary/Chris') ├── Node('/Mary/Marta') └── Node('/Mary/Udo') ├── Node('/Mary/Udo/Marc') │ └── Node('/Mary/Udo/Marc/Lian') └── Node('/Mary/Udo/Dan') ├── Node('/Mary/Udo/Dan/Jet') ├── Node('/Mary/Udo/Dan/Jan') └── Node('/Mary/Udo/Dan/Joe') Subtree rendering: >>> print(RenderTree(marc)) Node('/Mary/Udo/Marc') └── Node('/Mary/Udo/Marc/Lian') Cut/Delete: >>> dan.parent = None >>> print(RenderTree(dan)) Node('/Dan') ├── Node('/Dan/Jet') ├── Node('/Dan/Jan') └── Node('/Dan/Joe') >>> print(RenderTree(mary)) Node('/Mary') ├── Node('/Mary/Urs') ├── Node('/Mary/Chris') ├── Node('/Mary/Marta') └── Node('/Mary/Udo') └── Node('/Mary/Udo/Marc') └── Node('/Mary/Udo/Marc/Lian') **Extending any python class to become a tree node** The enitre tree magic is encapsulated by :any:`NodeMixin`, add it as base class and the class becomes a tree node: >>> from anytree import NodeMixin, RenderTree >>> class MyBaseClass(object): # Just an example of a base class ... foo = 4 >>> class MyClass(MyBaseClass, NodeMixin): # Add Node feature ... def __init__(self, name, length, width, parent=None, children=None): ... super(MyClass, self).__init__() ... self.name = name ... self.length = length ... self.width = width ... self.parent = parent ... if children: # set children only if given ... self.children = children Just set the `parent` attribute to reflect the tree relation: >>> my0 = MyClass('my0', 0, 0) >>> my1 = MyClass('my1', 1, 0, parent=my0) >>> my2 = MyClass('my2', 0, 2, parent=my0) >>> for pre, fill, node in RenderTree(my0): ... treestr = u"%s%s" % (pre, node.name) ... print(treestr.ljust(8), node.length, node.width) my0 0 0 ├── my1 1 0 └── my2 0 2 The `children` can be used likewise: >>> my0 = MyClass('my0', 0, 0, children=[ ... MyClass('my1', 1, 0), ... MyClass('my2', 0, 2), ... ]) >>> for pre, fill, node in RenderTree(my0): ... treestr = u"%s%s" % (pre, node.name) ... print(treestr.ljust(8), node.length, node.width) my0 0 0 ├── my1 1 0 └── my2 0 2 anytree-2.12.1/docs/installation.rst000066400000000000000000000004011452550712300174260ustar00rootroot00000000000000Installation ============ To install the `anytree` module run:: pip install anytree If you do not have write-permissions to the python installation, try:: pip install anytree --user .. _Tree: https://en.wikipedia.org/wiki/Tree_(data_structure) anytree-2.12.1/docs/intro.rst000066400000000000000000000120131452550712300160620ustar00rootroot00000000000000Introduction ============ Overview -------- `anytree` is split into the following parts: **Node Classes** * :any:`Node`: a simple tree node with at least a name attribute and any number of additional attributes. * :any:`AnyNode`: a generic tree node and any number of additional attributes. * :any:`NodeMixin`: extends any python class to a tree node. **Node Resolution** * :any:`Resolver`: retrieve node via absolute or relative path. * :any:`Walker`: walk from one node to an other. **Tree Iteration Strategies** * :any:`PreOrderIter`: iterate over tree using pre-order strategy * :any:`PostOrderIter`: iterate over tree using post-order strategy * :any:`LevelOrderIter`: iterate over tree using level-order strategy * :any:`LevelOrderGroupIter`: iterate over tree using level-order strategy returning group for every level * :any:`ZigZagGroupIter`: iterate over tree using level-order strategy returning group for every level **Tree Rendering** * :any:`RenderTree` using the following styles: * :any:`AsciiStyle` * :any:`ContStyle` * :any:`ContRoundStyle` * :any:`DoubleStyle` Basics ------ The only tree relevant information is the `parent` attribute. If `None` the node is root node. If set to another node, the node becomes the child of it. >>> from anytree import Node, RenderTree >>> udo = Node("Udo") >>> marc = Node("Marc") >>> lian = Node("Lian", parent=marc) >>> print(RenderTree(udo)) Node('/Udo') >>> print(RenderTree(marc)) Node('/Marc') └── Node('/Marc/Lian') Every node has a :any:`children` attribute with a tuple of all children: >>> udo.children () >>> marc.children (Node('/Marc/Lian'),) >>> lian.children () **Add: Single Node Attach** Just set the parent attribute and the node becomes a child node: >>> marc.parent = udo >>> print(RenderTree(udo)) Node('/Udo') └── Node('/Udo/Marc') └── Node('/Udo/Marc/Lian') **Delete: Single Node Detach** A node becomes a root node, if you set the parent attribute to `None`: >>> lian.is_root False >>> lian.parent = None >>> lian.is_root True The node is deleted from the tree: >>> print(RenderTree(udo)) Node('/Udo') └── Node('/Udo/Marc') **Modify Multiple Child Nodes** Assume the following tree: >>> n = Node("n") >>> a = Node("a", parent=n) >>> b = Node("b", parent=n) >>> c = Node("c", parent=n) >>> d = Node("d") >>> n.children (Node('/n/a'), Node('/n/b'), Node('/n/c')) Modifying the children attribute modifies multiple child nodes. It can be set to any iterable. >>> n.children = [a, b] >>> n.children (Node('/n/a'), Node('/n/b')) Node `c` is removed from the tree. In case of an existing reference, the node `c` does not vanish and is the root of its own tree. >>> c Node('/c') Adding works likewise. >>> d Node('/d') >>> n.children = [a, b, d] >>> n.children (Node('/n/a'), Node('/n/b'), Node('/n/d')) >>> d Node('/n/d') Detach/Attach Protocol ---------------------- A node class implementation might implement the notification slots ``_pre_detach(parent)``, ``_post_detach(parent)``, ``_pre_attach(parent)``, ``_post_attach(parent)``. These methods are *protected* methods, intended to be overwritten by child classes of :any:`NodeMixin`/:any:`Node`. They are called on modifications of a nodes `parent` attribute. Never call them directly from API. This will corrupt the logic behind these methods. >>> class NotifiedNode(Node): ... def _pre_detach(self, parent): ... print("_pre_detach", parent) ... def _post_detach(self, parent): ... print("_post_detach", parent) ... def _pre_attach(self, parent): ... print("_pre_attach", parent) ... def _post_attach(self, parent): ... print("_post_attach", parent) Notification on attach: >>> a = NotifiedNode("a") >>> b = NotifiedNode("b") >>> c = NotifiedNode("c") >>> c.parent = a _pre_attach NotifiedNode('/a') _post_attach NotifiedNode('/a') Notification on change: >>> c.parent = b _pre_detach NotifiedNode('/a') _post_detach NotifiedNode('/a') _pre_attach NotifiedNode('/b') _post_attach NotifiedNode('/b') If the parent equals the old value, the notification is not triggered: >>> c.parent = b Notification on detach: >>> c.parent = None _pre_detach NotifiedNode('/b') _post_detach NotifiedNode('/b') .. important:: An exception raised by ``_pre_detach(parent)`` and ``_pre_attach(parent)`` will **prevent** the tree structure to be updated. The node keeps the old state. An exception raised by ``_post_detach(parent)`` and ``_post_attach(parent)`` does **not rollback** the tree structure modification. Custom Separator ---------------- By default a slash character (`/`) separates nodes. This separator can be overwritten: >>> class MyNode(Node): ... separator = "|" >>> udo = MyNode("Udo") >>> dan = MyNode("Dan", parent=udo) >>> marc = MyNode("Marc", parent=udo) >>> print(RenderTree(udo)) MyNode('|Udo') ├── MyNode('|Udo|Dan') └── MyNode('|Udo|Marc') The resolver takes the custom separator also into account: >>> from anytree import Resolver >>> r = Resolver() >>> r.glob(udo, "|Udo|*") [MyNode('|Udo|Dan'), MyNode('|Udo|Marc')] anytree-2.12.1/docs/static/000077500000000000000000000000001452550712300154675ustar00rootroot00000000000000anytree-2.12.1/docs/static/buymeacoffee.png000066400000000000000000000207001452550712300206260ustar00rootroot00000000000000PNG  IHDR(g+ zTXtRaw profile type exifxڭir#;s$ 17χ춻m1c%TDfB/~r=mZ#?e!7=~byiA>.e^6O >NOߟk| :{޾ O(4te)繼"ȯo~5kxb$>^coIx~~O\wxt<_3" _{Y*oD=Np"h* x RA}[s5,7iT\ܩ.<5^4G*ueY[ ?+AH/xcgV̻Yӹj2f8ɱRoM ,)Yw~(yȟh}eRiU'=-t1]ޱYT| jѺtmj W /s0[e?mR.:HoDm!oiH#me~2g+(z xXYтR^7n+XJ4L\Bs޼Wn/Y{(*7k>s:•:Tl]#ӱA3ɂl27sʁ ߵ^beZ[:C˜V!@eyJP;0. N|%\hiOV^hdH> m퍮4' !ϢaH .#\:u6BrFւ)p<`#QB&lg/ܴq̴6-(vqydԍ,iEf2vPWbN6AyBa]ա4YqVC4Of;98f*zւU›q yb1Vmjo0OF9%a;6Mh(b$5~mb+}y]:v"/V gy$ec)RNW RT )dRs nK+a5No}HsX:ju(tk_3 y4Ր2;Bꨊ9"IpqwܢG]\cKa"ܨ܊!˻7"t-&%`K* K)[4}ˠ0HqL-km+JQQΠ:bRC364u$i}f3)NSU3= w85!E '%nzyO&;Z8f"'7b҉ !CQs^*/M^!8}cG xz\6fy[K;bDZc |8/d2[FȺ,7kPE3DDU`L=IF_j.5^]I^.T|.b;H gw`lhE5&iP%&ʂTptS`o^ٍ#hHa)ـҐ(EllBlm?Swe[)rM"{ <7lZD.JFH!uzdGijPyccjMC904] Y4Ҏ- 8R u/$j.nr^TIɧ4C /nIciH -_e6Wod$HnSݧ0?WXNTrJ'C4h@t?(fs|¥֥ :=c ]^!CT~BG8%+c78D/s D# !57)RnhF^ sBZ `FnC'Z r.X2`#q ]=ۼpnu(MwϏL R0ZmBQLufh,aq,lx7}]0 ^~0( 1!dJ|c^-A[p!y T/K~N\ n`yV Ƀ%Ɩe0.*n刉Cr8]BW9B]#@?8fa): 13Ơ?$-ؼaL%aD\jf7F<7 vE0cnH+,9+; 8ժlJ^^|{#4 Vhί]S[ >H\`Oi^Vt" ,(rڄ !>.!ea K!l>0&\[ X J;RO> %]lWHp_Ag352map=RXXAGڗAحu_ ")p'm 𙊃#7`|(@K%RPa Da :KA%Ib0EDnB;]lqPFxh[~BQg(;An@r;Y+q8;J|π|bkiCCPICC profilex}=HPOS"U+:dND8jP! :4$).kŪ "%Zx>λ}P+146άWЋD1,3˘<랺"<˻Q|",3Lx8iC &]o xfL%Cb6fS#&NBegTa{2i E,AQ:)t5rȱ24 ?t1q>F.P:O3p0Izm⺥){0dȦܐ\x?o@;9N*qcy^xwWi\rBbKGD pHYs  tIME7-IDATxytTe$} vPDezluNisܚnifġqDPQc$ K,{J ${N{R~|wS:*X}] p} p} p}w^QTܽ(L}WfSidy֧6scz}PU+ɳ<(=Go9i>ŵNl)d3 ZBBB7pщmlc_]nOQ z L&70 ̌0:@dxNǽQTΤq#L!>w :PF(^uzAl8R@KO?Ia^z%U3[ncr Ad%`ojF֭pP>|t <g;j2}[>}3$Ww_pᩏ|_g@CFLA~$ ^[ n`W%THm꼠H2NqzC@_(^ݻ;LJd (,~`24(9Z΀goۇBº kiqѣ h%_VhCVBH'fyugvu`P[5pv/|#Dxi_qrɍ Fk[ӹ/4멶b y;`zicXdKvow 칐5]XlB@L^@vЛ s> *5?L`yևgp7L^ D>]lOu 00S&<n&Y0c=;L CE&{kAHT׌;`,  mMpf0_d΅P_ o6 x;reMLVVVHsC^F@@-,r@y>zJTZN烟ɋXj؍/ǷwKM`>/5L„bEJL^ @g3½vNXj@xDV`w \k 1ky1flSYs}yñ=2ʊNnX<&-– ў ;\p[^Sر\pY5`RAPg70SZ;@P*`㐕yE3nCgKZwXďNtڠCf-]P.Mg^7zav_UWCvNY1L_#e^wMCdt[!>Y^G@ErqZ}jS2ʳ8?T@D)$6)r#(k&!6IOYI,: sX8Bi2WU5wٺ`P>X& ȅsx(?ӤRB9j MACZwI 3;z˜68٫<3%;d;aVu _[w >£Œ:l a>UY?*.2q Z:`@UꋄR`*>i$Mv9#8AUV3/",yӿ`g&B 7|GE|fLYnO5 x PK.[ ^KyM5` :FE:=:I@(^[(0~sfvd&k:HQojW` }ڤkk%Hj)N*J`cXAyg Nh>-y?z}9/єhbё9N.pxmB wʶ浭acBȎvk["@SߡJ_{LI7`olx*7&ğܯ0L{;LuAPsEUܸbe`kj 1a Cl 7#)C"En}@dše`MOV{ZLv-NZ.SP g7CD :86>/vI `:NPuL f='iIl?)2UE>dffYs {kmśI5 M88asr&x~ c$T7ܰF"SaQx5O-$3{ؼw?>y 5 ¢w ~WG 9㵢.,C1.Sig!c*PmUtʧH?hm@k%بs3*l~ʤL- ?[$3.VmCJїu\PsXIN@pJ_rLh^؉`>]@P_z%2wPN<ɠAz=ׇ}Xrx 3"qTi^[9  !w ,W;[AMXTFIu$GjJ#+eաU pBJy%0xH s|EKzؽQ ϲp#0n_a H΀iڤi3þ-ŋ;zAdZ!o0`;턷zx܊[]Va+: vlp&qB vìNY D`Sh(׊^9,B,O^K!w:tbRUEϫ/6UckC^hq5ZDG:lIW~7I3Bp_ZDo[Ċl`  pkismqmԛj[z&gzifQr-B'x[EQxk>_5yA]UYEy-`6k`9 颳vrdL"oe hF `NnLU,6VѪ,h $ 8\DW!ҲEN1Vkޘyq*KWF6,~Jl5[T?@yZ? n+7oyriaQcBPk8ULfẽ2l0)RG̎M^ W}27'e HL} O׬tbgKaG9u mj zb n%8A<̀`8 g|@.>>!!yp4222;;ȑ#={lݺu||Gϟ{4XBspx(, F"莺r@(,?"Wr@(,^p=>"肆怂Q!X^(E8+Z9`A( FA'`9`A FA`z9` AS FA`P0 ZX怂Q$X->"HP0  7|s@(h ,Ĉ FA3`dEP0 %倂QX3`$CP0 RE s@( Ad )X9+`$@P0 ҁEys@(Ahd XqF(Y"r@(HAD FA|¡P0 E m瀂Q,@(___`oݺu>}bcc FA\"6mРAM6=w\#*;;!UUUyyyiii ͦļ IIIA~  OH(FA4Q6"_sNE60r]AKa̙i&U7 RRR,Xl2޳ E w Ol˫k׮k׮iiiǍ8|J0 I~Q|gA]{ 2s%XVHJJ*,,_nhLHHp(hYYY%%%G UvfbEz9 Fœ0qDR\\츅j:n=z4+>9F;Ǐ3ݻwb4o~q;t֭/񅡡?@nn%''/\p͚5?8 X\p!##cݺuc'O@޽- /Zk׮M6$&&㩄ǟ=<<r;vLNNV'E@>}RRRV^4ݾqF@oXtԩS]F?@BX怾Ԛe?.?3gnjՙ-[={>٣c: 6QNZn]FFƔ)S6l`0d2eddZ_9}͛U~0M>>>?_zS׮]O~wϟ6mZHHb 7ož={:u.\H~7̪4,t@(p?z>4wD @@(` @Xb(`D ڎ@p"(` (ڋ@ ⢥(`d 4ڈ@"#{0rEy9,́`A(`$ Ds /X? "ȇQAJČ@`dE(`Abĉ@;>#X \Oj }v^3 JE;vЋ/f 3O߾} U$&+++66b4ccc,XqtgŋC w6 N&7olѢ!&''}pBEEŀ0Æ b38X))..ҥ O(̢P+;v#ũ=XsڛCA k4l6l0:uf/ s"Egz{{ӏڵ妻w啐lv}ĉ4 =z())ai~̫Bp֭ÇeƏr999eeeofE=nܸjf[GFFF޻wbbbΝ{"xzz8pgt.K;&Ltoͼ'bG```ժUޅ8 :tϚ4i{"HMM={ѣGyOw|x'Xu;vիW`Ȑ!6m=L4kСCO=o[~=A]f͚u1xwMA\]v8{"EP+VмysaHC۷/X]]=qċ/H`>[`pj:Hb&OLe>|0<wyD$999зom۶K JѲe˃@RRҖ-[xO5J?qeyf9sfFF4A1nݺf~fZj52dիr̘1׮]=v"(?7|,˧~ڹsgiYf͜9 VPP{"EPƍ[^=.Xf =f@2`ẉz\/K׿(ˣK_l􅟟߁?^b>Sfee@=RSSM&tG6meϟ{n }}O>8'M)S=`dϞ=K,ooﴴ4zNqG~wlĈ,|1p}wʢ1;w =Q)***,,tfݸq9$$b}~ׯo߾^ '33~ii 7olѢ{7i֬Y=bƌtoaذaUUU'khΗ*rк^5 2>۱cG8tPBB$wdr[=z{"^,KQQʕ+M6ƍy#X0c Ξ{B4OO90#F=]c9qℇL&{WYz5=r``ju/$)>>>_>22ݼy3TWWxO$$tQڧo.--=2$&&om۶yyyǑ,“}v(ƣGs`X&M{.Ũگ~+{" 'pȒXhk{"3fCee 8,FqǎzsBx%<$4t=1cv;ѣ΋! P BŋGtu344T9z5kTA}j{ u2o޼}@۶mV+=Nc & zAѽ{={L&BoH`x'IP>CҥKQǑ-r&99~ׯ_=`>B`ރ>cB5gff2իWGQQQ֭>|8C:5~xރڵkЯ\Mಞ $Չf8p ='̶ַo_ƞ9R\\ܵkW#e]. -o0Hᱴ\ K.9xoݺކgqHMMuxyy9sFHY", Z4 =z())Q%,Jį^/_ /RVV/z.4F[rrr}F 3rQPPen:tȍC̿ Gq[fG}D~7䂥g|r@@] !lرu=9 }7$޹sqyk@8d,)6 CCCf`ի<>JC%.Yk׮u`Y^{=<<==O$seW\(K[vlؽ{[ƞegȑf?}0w'~f<)"(.JT|,5ׯ_9r$ AϞ=Ϟ=xfp(TWWoٲ^ +,,L۞U,eJ}||>c _@!￟>}`Ӷg}RV\*gGzмgU 2hxg6ʳEJE=Ao.([ :dGbElЧgŊ >+zfn=+S)Q^ z@$Gtg6sc >t==7HX33iLGbg6gEЀ>==;pGvg6gg\X3s VMX3smP X3cqGX3s]T=|#7UA"h3^uC=?4((d2)bUɳ)ZLV O(OܓӧO5qxpR4㙈uE n)E]\gؼ"rŪeJрg"jWyPD\٠%|@]g6h3"FKAg6h3"FHD,g6h3"$O"3d̿' R{ey=RDzf*W$=AFb!QX} g@jIYԠg6Y"'< ZBHrr`ԩS```NNq4 zf,MAHzĉÇw֍8=AB#, ?E@GdIENDB`anytree-2.12.1/docs/static/dotexporter0.png000066400000000000000000000603411452550712300206400ustar00rootroot00000000000000PNG  IHDR[?@#bKGD IDATxy\MϹK{vRRTZ,-%R>(_k03fa c C #TD= iIۥ}o{;wy?t>`l6/o> /R__ӧjPuu5ŒP(t:]YYYYYJ OOiiio޼S}}}o~VIII]]}ȑzzzzzzzzz:3@`p7W__VYYY2DYYYMMmРA4 !DdrcccSSɬ*//////..cVVVfffAAA[[۠A̠gpЧ iii122>|8oiiyccc?}:k֬)SP(p }ob2aaaϟ svvvqqqqqQQQNݻxb///}}}ϠOЅW믹VVV- EEE/_>sL^^@Kkk嗺Kn޼yĈa|䉥ǏO`ݻwG'NۤB$%% &̞=;??T}b0ϟ9sYVV/<622:r":7(<@4ܼyð~o᝖/[ !tʕ%MMM,-- i]tILLbqekbi ****T*۱֭[!WW%< ܥR]]-i *륤8ߎt/%$$dee;.WQQA瑺LXWW-i *>͊ڎB=a"ᗶ)**rkABII[o(־9**JRRU+ ZZZ9S^^N$$$> DŘ1cBIII\}6mt޽ڜE'{\!djjSTT_PP0~~IJJ266c 4jjj#F߀ ܹtZUU599y… =ZjU/W@5jF#3e,.~oRSSǎgccCt@"۷< DѫW/^|I!t„ ʏ?3!y3@_sΥRGV]VVfooO"_M5cƌӫVjSM4-::}p0> D͛7CBBˉg=~ @M6->>$<<8ҲsΩS:99ƪ+Ч300HNN4iʕ++++NԃTss󀀀#G\|YRRD> 믿nܸJt.|qٲerrr/_\n@_ ?wwUV577}a#G|˗cbb0~޿wދ/mܸq^Wn޼yݺuD <tȑ#Nvrrtss^|… 999FFF[lYp! @AX,ڵk &<}L&O8qڴiNNN#GݻkEEE}}ׯq}w}1̕+W^|ʕ+݋VTT7nѨQ455dr_wАܬ3m4WW׉'R(Ǐ={vҥx>}.,\nrrr"#UTTTTTt:Doiiijj-///---)))**z=ͦRVVV֝.c۶m;|ɓ' t0k֬{trMMMvv۷o򊋋6˗&YWWڊwkIIIeee555uu#G>o~-[pi۷D?cƍ۶mۿ?Y|eԩEEEO<=z4qe4˫N=R''XECLLãٳ  *,,}M6k֬Fx 4uiii&LԌVRR":NL81:::!!aԩDi Ғ&O;v'Orss>Lt@ǎ6666q7DGGN88> DThh͛7%$$O#GkiigF@]re˖-x񢠏n:thllsss2@:ujɒ%7o>~8$ ***111jjj&L :I ;pu Ӕ^>4004iRbb"q\}??C 222w޵ttt&:;>@$-[9r^^^Dᡖŋ߸qٙ8Ni LU+WwF ,1c׉`_ @ZZZ<<<ܹ2c 2|`?Ѓ> Yss >|@tarʚM6OЧЪ9sfjjGƍGt"TWW޽8> SeekAAALLqKׯ_(W O!TVV6eʔ#F`k׮hK.?cjDi l>|0y䶶---EΛ7>U*"O;GGG <8ӧOzzQg̴4hӧOI.<<ɓ'uuuD i $RRR&N8bĈ(GGGzť8Aӧ48|ɓ' 8@/<{K.QT MMX2<~| 4`.]3gʕ+/\cJMM-**N?͛7Dt 4TǏ_t-[;sw􏲲ǏӉ<a Crrrvvvϟ?':3@m۶m߾= `֭DҡNNN>$:_OAf7mt?ۛ8C\\<$$dΜ9nnnn": 0Lʕ+/_NtaC&O6wsyzzi (ZZZ-ZDtaX``WssUN> ACCìY###mll#0 ;x𠢢5kjjjlBt"Di磌]]]߾}iaaAt+##qƊ bAd~YYY/_L:ɓ'G&*Z~=F򪯯?ra[RRFT6D \ ?оtҤI山ФǥKN:fվ|C y%&:` :IFF&66ظp ÇC !: ;w3TCnJ"Nz= OQ(YYً/]VYYJJJDuO k.AaL^n]AA !n)))#J癑L/BCC (ϧAX,`0uuu555:M}M999s:;;STS^vٳg:::+WټBfffppӧkjjƍ7w܅ v'-\pڵ0X6juuuDrqqsN[[ѡ:{Ն t+N),,!D}zzz!B[n-,,$:QN>MP-[ݚ߄İX,ubEGG ,,,ODЧӍ7 &++W^^Nt>hmmpႮΝ;NM>INNvss0lԩ999DϠO ;99aQRRBt~jmm=|0N:t("PKKΝ;Č>}Jt{ >zΝ---DO ׯ JJJ.\aئM#r,,,9khRRR  0`26l3g󓓓mllN/_>{uqq1щDȝ;wLMM[[[SSS7nȣqVB455@P@ kN8!))It"nZdɋ/233#N81{ Ǐ984jԨy͚5+((8oꬭC_|?~]$p݋a޽{[?#aD0ω`kkk1cFJJӧO}ךfΜÇG8={vժU'NXf Yx?ظqs-[Ft `[nŋ'nuuu&MMJJD6>tqqٱcǏ?Ht?GDD888ݻ'O &oaaؐ!Cz\9==UNNNVVٳgޯLXXXMMͦMХ/_,[lΜ9{Br~uO?lٲ*7}PUU]lջ_'!!ARRr>}XjBy'sB(44N-ZN<9܌h4L|?VSS[t)༷ھ}糲<;fob|_d2 rss |ܺaKKK333{1z<B-222ںufiii[[y9sӍ8qɓ6mh>}͛9s# .,**w'[޽{wvv۷3fڴiDٳg;d\&}Z Xkcv\ɖf̘q1N6p7o[hz[bp@ wrrtss] v]&‡bfkk|}|כruuRRRvvv/Bu2H]]!sUWWsp}*9xg %22r@9賦&))3gp+WȆҭ["?~ܾM133SPPؘN&''+L<!rssBg L&߾}퀅 :99q9:RWWud/^ ~Z7443DEEL}tر|.jHKKoذ!11>%%eɒ%bbbwI$ٳg||ϟ?_>777((HBB lcǎ|;pDԓ'O`G W~ZbE!.}f͚QF]ǍbWZrmrr~-$<==暈RhhhPPPHHȑ#Ϟ=FtHJJYz_|immMt"QD"mۖ;v  ѡbcǎ:D H~xrƍ EGGܹs---D'ϟ ƏNtf,***[n---%:QonٲEZZZUUD"577 ނ>-󽼼TցˉNM---׮]CY[[GDDtVWWw!UUU))5k𴴴իWKJJ:tf{{{KHH޽[?"{ƍtآEN-..Gqq1B7555119ra84///777###--ŋ&Lpppppp066nF}}}lllTTTtttzz:055533֖kkk233RSS?~H"ƌNǏ[[[\rLWW۷?O nԨQW3#秦O 2l0555%%%%%%iii999 $$$$%%LfMM B`0wDKJJצ0Ыs玟ivvvaa!>SʰaÔH$NGUWWX憆QQQ`0߿_^^"ɚ_vttf߻woϟ?6m>-***V\y޽ 6F&W x/WTT1b~<ڵIII\fhhe˖EQ(^>-l>|l2 r &nG]zu޼yP{QŵM0699YBB߮\_HŎG&رG022 qss۾}={*++y;Ы׼zȈF]xq`z :]]ɓ'.\0,,,;*++Ç\z{{zf;VZZŋK,!:]***Ξ=;0%xzz.\peee<ݑ={>|?_~}ឞ999<)8ӂe֭~~~Ç':]?sɓ'544lǏZ|9>}:11qԨQnnnBynWO 7n=0!!aϞ=d2D@tUTT,_s/-.]~ڵdѢEMMMk=zƌUzwO 6><==}D'oݺu۶m&M"* ޭ322LLLVX@H lڻwlmm%$$fKlvkkqLLL2pô;ooo)))%%ݻw}? ӂ!88а*11f|b޽.\\7`ôXXXw=ztذa>>>F 4ZhѲe˼RRRNd iuOIIiϞ=߿߻w7!Ħ4_=zt\\\TTT``8щ-4i҆ ??L{222렠$}}}77$sA}O577999YYYټysyyM={ Ꞙgff۷,--a5=(33sܸq'N8qĵk Dt"AAAѣaaaDgf:++WSS߿\@n=ztfffLLT*щ>XppMFwwSfddc}?ô7tP|=STTDt.O,99y̘1W\ !:]kkk[dΏ?HtaZSTTlr}-\D>M&okk;tׯ_/XDt=>=|u^^^PPPJJѣ}߿߳gO?uuu>zsV!ׯ_}vEE7SdA&kLLL F||/U|?=> |uBB e!TuuΝ2Mt"zƷSfŋ^^^tzpyÇݻw))))3>zbVkNM⪮&:-mϞ=&L066~ щzOeVA\k׮ :tOii)ѹ@湬,KK:t͛D'baZSQQٳgχ~駐MM5k|\OVppرcdrzzqznVh4Oaa#G߿$:&ӼRQQ1}t// 6>(((zOiuO\\|yyygΜoOt.Чy"228###::z5/mmm/zB_8L{T*r}Νϟ?[[[Ðk}˚|||mmm_x1ag:Xiii 0n`s|3ڈ 47~… /^ OzOiu}ȵ+!׍D}};lv``W/^Lt"CЧ=cc77۷Ctyzz>{lǎ?L&:O=z_䨩_jjj ׯkjjۗY&$$$##Cg5gϞM8ѣ֭k_5dByyL}}}Lt( lk׮ 4HOO/55,/ts 5a7onlldDP?DFFnhh0 [d ѹx& `999D'=>իWFyxx53J=pATWW~(^LLҥK5Z[[FKPBrrrL&h|hiii$&&&|C73%%Ç0BO}FRR2 b |0b}}l rq>SSS JJII:utW:244$:@6ma.\hmm%:`w B׮]… 222%'ή[j2\VVFt.7=NS3܃0 U}P(D'up޻ 1 [bEqq1B`̚5kٲe^^^)))=C<<<ΉdGGGeee#~xһ#D"vq3gUHOE8CCӧرr]YYIt.F GKRmmm###Շc\$ŋD}GT*UUU5&&t*$$DVVW-d2y֬YD#Xyyݻeee?~Ht"QKee~EEėH$;;A|XhQ||<\JJRX'9sijj": 3---D0lƍD'# 7n0 ~D'S . 455mT*UQQ͛7D|tcccee/_gedd$$$9<33[b!{P]ǿZnpݻwÇ0LAA˓'Oȇ (ݻtJ6?|N+ szhh#G{ BhO<=z4O3}-555o߾-(((**caaaQQQEE(!C 2taÆihhx??~ڱd2;v֬Y>>>ݼhS Z^pիJJJx¡|ɒ%>9r$]t͛7<`0ţP(O<|,(?G \fYYY^z۷oB eC:tʠA%%%)t:?U[[mll_I2A}}}sssSSӎ666vҤIaT*NO/x𡯯/ԂpP !(X`BNdLBDž>]__ڵk8@vQXXHt>ZZӧO˗O2EWWNg %$$..%%cEpԧsrrŗ,Y#u-33sΝjjjd2yڴiqJZQ ^u3fH$ ~իWS(uuݻws7޽aQHH%j?j>/\D"_B{iii3g0l̘1< :ο@-Ԃ@-Ԃ+ЧWiii]]ݫWWJ3f ̙STTDtԂ/@-ԂpzzzRRR#--}1c@-P ~P Zܧ۽9::E~ ܿ jԂ@-Ԃp=ٳgS(_~EN\tǃB-P ע>]SScgg'//m]*** >6Ճ IDAT݀ZZk>][[kaaK zggg>@-zP Ԣ>bf͚q8x >z+xŅN888u3IJJ:tqͮ?~d;ZtVNFA&_ j\IP=(d544KFos|ٖsHKK۷|MjѾ Bh֭\ -\p߾}i'U xjcӧ;-okk:u*}ݯZtѧMLLj?Fmܸ={ _޾˿ϟ?/***8O?|Mu$|EDD 6,99!@-lv4ԂܪRҥKo6͕ "ŗ/_BCC.]Z|9Bܹs>ƎZtQDӧ!s-:ؽ{7olʕ+tzdddCCCii֭[B?n_AZZƦ㏘)((t\bllLjkkbbb&Ot{nn.BԴ} ~N#G޸qGիW_Pւf=zEEJP(eee?v"^? ZsBMM !^j>]YY!͛~QE/A-Q}y-&N߈Zk>e`; <<<=zTUUum6~Djii#]&8۷Cv\ǐl6!лԳb) e-Z[[gϞu:i&q\ހZO-QŚ5k(׻mH$~,PMMM]v_'N}ˆ ۗ<˒* K:}d2؈/ikk5jF7&ŋ#"&&velM(kq ==TэZtS 6բƍ[|9!tĉ_|TVVww6l؀:zhmmm^^޼yտKKK&$$uy^||Ă JJJ j5޽ïPWW{a{l-6mځ iaat珗 [GP l6ϏJfgg777̙3jjjuuu?^vkѹO>|X^^i`׬Y3j(|<ܸqもX,V UUU+WTSSMNN633vW=))NFFFRRrĉ_!--mԩ4MFF޾p`&++kllm6.AtE"/twu `CCC>yĆZj(E$ **++;nܸfi>բs铤έ4""77W\\zԢj?b~;wqeFY1}t}}}΍̆Z Ԃ@-Ԃ]Zuuuww@7.^4޿E_A-Ԃ@-G?j={&..?r)0KKKڶm=Z_-l6ɓ$fO2w{ZԂ@-Ԃd ;ð-[HKK[[[wKIIqtt1bĭ[}ހZj?j?8Em<00ð7r~彐~ԩSfPoZZkCfW^hfffyyy؁illB֭pހZtP \E}f䘚h#GCkhhHCBB hP ܪE4njjڱcؘ1c_zzzb6eʔ|@-j?m:;;#N`޽[NNn.\ :Z@-P ZOܹcff߭6>>+9SqqҾ}5;A-Ԃ@-Ԃsl6b#LLLN>͇GXΞ=J*++ꛠj?j~vϟ?𐐐+VxH_޵k׈#B.]P P N4`:thر!EEիW +&&? 7mڔAt~ZZE_qO߷o)avvvKJJjii8w̙+**72LqԂ@-Ԃ@-z cG>|Qiiq,--MMMH$/ۍ/_&ڻӠ7 `(; V$(`"vYvTq:hQ)V[q)uA;0jtD &">塀ȕ~?/9/9d2.]b ++Ig`.sA \lrT*OKKKMMMMMe2 ,3gѬYtttdƊ /^h";;;eeel񭀹u`.s1 Aڞ>}[PP_QQCPWW'X,ADflP(HBa??Ann̙Ϸ?dTu`.sAŋuuuBBa{{{GGX,&!Ch4a0 Sadddhhhhh8k֬ Eԃu`.9ihqqq6lw!5`ԅ.4u! 9 @]iBNPrԅ.4u! 9 @]iBNPrԅ.4u! 9 @]iBNPrԅ.4u! 9 @]iBNPM*ʻ:s̥K>{LWWWMM|jll|96..@Z[[sss?d`*hþ`0l2 {aoo54h4Zii</`ؼy F:hѢ4 4L>>>CC͛7˥@N}:L&b1 t:&" žrL>hllP__Ov655555511rFFFT ')))iii ;; MMMsss2 ɿs݉&ˋ EEEeeeRT]]r\;;%KLZl#`466>x 99 ޹sr\.k`` kii&ODbddtRjff6q[GN_>|gff\\\?I$D>" gϞ|+WX,n9 %##~zUU'm?DIKKKHHHHHL&sʕ~~~ӦM& cϟ?-,,455\p!Fwi*>>>...))IMM; y"@f|~TT͛7l6T<ojPUUUׯ_~zJJΝ;mۦf!`z{{^g//o:{kTTTv9AR7Ëkkk+>:::22?ETwܙq?TQW[[[dd'98oMRۃ9:|pii-[\PP07"`l---cbb][C]]="""##~} r &&ӧ[n-\.7555<<<$$dW Oh?~G}[ Ie۴iݻw_u*8>eO?ĉ111_Bĉ4F2 b[l={6а>|II~%ww:a N xԎ\|X&psss OO۷o_999ݻG̘1cPVb***)))cѣGA===qqqnnn:::L&*22oL[F MJJ*** FAp۶ml6訩 !TUUy5##ZII)))i_u|ZWW є} =T__Eɓdw֬Y؈4Ç-Z$ 1Lmm툈y8"ѣ<b_z{߾}cg;]\\<>cƌ={FFFu#Vzebd8ǏwؑJ.,,tqq8=XYY QGDCCh:^[lll$I^^ިK33ҁ-ixeܹs111o޼hjjZ kiiQWW7 MgHneeN2'O[ݯ99 pd8 F HLLlnnuT*:uT:=-CDZȄ&z4oܸ1MTֿmnhhPWW؂WFcpc0nnnnݢhtuu+++Լxb8mmm'OlllȳFc޼y}Yff?‹/nذܜ ޔ{Μ9\ZJVV4{OOϛ~X\WWwqTlٲWWXQUUuٶ} DVUUݽ{wZZZ{{{ffRdd* }Ywwwee+W\]]\BvSPPpqqhhh| ! K$ǯY_=///WWWYhaaA~!::ܓLjnn޶m.trr>'|A>OOOwuueXL&ٙKA`hh`0"##b>dmm-[c9{,jii؈H\]]oݺvZy2566ZZZ[̙3ۑ}ѽ{rss544]˔\3ǧ5N:E}||"[TTԵkע4p8wܸq$ܒի| y3AzVVVwܹ[[[]qܹ[:th蝲H8> nݺ3g޺uDb]~/ï4֒%K233MȻXNN駟~B@N|??[eee-foo?mڴիW9 cd2Ϝ9+++†7 w5++SN%''枞ix/ pœ9s;.۶yyywG8 ƥܹsǎ#? 666wQT!N<ɓUV}666c9 2p> Zl(SRQQѕ+W7mڴo>. @f$?c===?? ʻS]]wڵt}};wn߾}w 9 WXX[\\lfff`0]ڄ(((葪e92r&PZZ lKƔ'$$jjjXkʲ r&Cii)LNNxK,qttaMT>i4WC!`RI$̔䔔FEEE333?i4+%Avvvvv@ hkk6mSWW 7R({j L?̙=k֦ꪪϟ744bmlllmm\d@!aYVV& W9{FvcOZ[[qvuuuvvbH"BH$jll"ߨldddbbbjjjjjJ~Q={`,VrIDAT$555͢bqGGٿ?UUU`0,`pȤӛo9 @]+Prԅ QiIENDB`anytree-2.12.1/docs/static/dotexporter1.png000066400000000000000000000541171452550712300206450ustar00rootroot00000000000000PNG  IHDR bKGD IDATxw@wo a2D1 E78jŪOՖjQ=88P@E+(( ˓r˻|ǻ]8E Dl@2<<< Э[aÆ1}0hɓ'3BJ\nUU).^uu5!RAAA7FB(P0Ba @ #!FB(P0Ba @hii1EP(${{{--Gegg1ڵkL` F2OSSf9z޾}z]QQ!@QĤM2sOsef #EqMBȠAgғd`FҥKc//N:ѓ7o|7=zСCǎG}-54`۶m4..^'m,Izz:!H|!!ɓ'm@0aEQǏ',Z('''11QYY?x@$uuuggCы7ח( CREQTuu譝:uꔘHOB444ijjB޽{'}zʕ+GA+sfgg>}zرmm@ãk׮WnAoW[[KWKM_p8m2 ggȐ!u\x.ÇWJҠ QQQo߾'utt!mI%@a =u.cUTTiiiϧ7۠EnnnNy/^BLMM[*y1|>X|˗/ !ݺuk=)0@@IIIIO:;;~3 #&M8rќH7j(IB+++4^} .//Ԝ9sXR"CaM6u}ٲe/_...~Ɍ3e6 8ɓ'999 YYYÆ ׹+MIIo߾;wn~~7o/^qA555F6mPDBxu.{u-99y_uN RZZzƍK؀cKK޽{O:߿w:w}gQQQ3OiV^hٳgNDp@ #!FB(P0Ba @ #!FB(P0Ba @tw9#D#H #r^^^L####;\JhaÆUUUQhرǏX&>>^IIw+az;e???3ȏLWW'O78ux+V 0̌8á( r"&&fʔ)!!!| qW_:u*66ʊ,Kim# )..U!dΝ666cƌy9Yg>Vee… ?qƕ+W2G"߿WUU`:[0(&M~``;qZ ;;fذaΝSRcBp) c<}!;;;::Z"BH/\pʕ,l"##ܱcǻwߟ8pر-[0PơCF=rț7ovڕ87mڴ5k,Y$22,#(駟VXi&tEQ̙3#""LMM$F-PRR2cƌׯ=ztڴiLi3իW;wf:cPH*??ر999!!!Licׯ_WUUe:30@"O>6lXaaallUE==,\,Aaм;;;==^z1'((ԩS7of: 3P4… Æ y܏5j޽{׬Ys0@S===ϟ# _x_~tik.[lϞ=۶m[|9qfĉIII,A\gϾxSLt;88|H #ʦLsEWWW08p`hh(e:4`(,,9rdRRҍ7*"|W\e: 0#FdeeEEEÊZvڳgYAϏ T[[ ӧUrc!F244?aE@EׯY #rQFYZZhkk3FصkWFhȐ!.\PG8Λ7o|~tt:q_B}ŋ*ڶmp8FFF6NMMuwwrqqkeN233̙jѾ%a2(1cƸ]xQMM8mח(++f[&%%kii=z(;;dĈ׮]k٥KBBB mffqV^K)1zcHM555^ͪ7oѣGףGooƖ>|'OJ3mXo !?}JJYP" 0aŒ3~w6e5Deeӧ\|Ν%ëVRأ @v09w܃*))͛7 ! IOFFF6͛ݧLK(fQoPL^^^3gܻw/ҥKc//N:ѓ7o|7=zСCǎG}-54р[ZZGs ֭377WWW !lذ^\t(""WU|>_]]] NOO'DlhhHyIӻQIIѣǻwnLo FDDڗ_~YSSXBoݺUZZ뼼ݻw5,,Ǔ&Mp8lEQC@WW7!!׮]+++ϧ֭[MںSNs|@ -..NNNСCTT݀%D222!`wRƎW}+PYYYlիWΝDUDwxxxs!>}Z4KҀjѱcǎd˛"*(z!ʊl0Y[[7kjjj~嗒4V}+l4P׮]?~i:$ɸ!CԙsEBh^*I&DEE}Ύtss_`Abb"}#F`Ch_~#BJKK'$ѿǏo߾]EaBa֭[&L6mÇ%m!>YQQQTTVk1mТ{9qDVV6FӥKBȫW!愐ϟ7x!E?;f̘-[|w!!!W} ȹǏ3FsE RUUgu֭dp8/ƍ.](jҤId*++),,:sm+\ BRRRГΒo}||f̘ܢ[aL_hG7n\ee`>|Pg~ҥ8p |>ѣGɲ2$cjjj`` SQQipLjjhNq0555}100mluuu޽o~\]] rrrk8VF ٳx._)>x/_ҿaz,YBٵkWqqӧO ykhh888$&&4xTBBڴi |||\nDDK\QQQ߾} PRR}+@0?|駶.Ԃe˖u]EE5*22E Ӈ allgaÆ9ӻwoY;/,,xt+Wnݺ;w&sttQ=zS-ݥKwwj o꟡R_9ŋÇkiiݺucǎLQ,qqq_tk7#GrW^EU$}C=qĶmt\)**rssoJKK{t4eeennnYYYo޽;qEQ_|EXXX||q$Deeqݻݻwo)//wrrOJJܹ3q$1F &O%BCC'MTQQt0WSS3sW88_zzz>3gNNL@a2_WhhhhhzK;(((88xƍLgh #m~~~Njt9r߾}?C`` YA8pܵkɓM~ywnggtF4U&M[v-Yy'NOLLѣqdRttۜ9stTIICUUU|||  :vtPD| R[[;ṡ*R4<ҥK>|0aBee%q@0v pB߾} ֭[xx.\tPD(E8cǎÇE'Nغu+Y@Gd-._%%EKKKjiAqp~KLLLZZڿ/GQVV>}˗'O؂-ZwD8PL8c͛AϤ'###XFжZwD8PL(QEEźuuuu=<>~Ǐ1QSSMyyyBHii"$-**۷vvvY9%h(ў={N8쬭vVSN~!]t!zbnnNyx/^BZwbtCAa8_|qƍK.Q5iҤ۷())UVV/BZsl 2-Hз^t\rE@__>CYܿ_4_ZYYB\^^N79s挱{;m-HA-\^_)rrr:rݻwdff.]|khh,Y$))ݻ3gС?Ç߾};w7o,^8##jjjn E&PBqnڴi޼yӧ^~݆+B|!5&55ǧwclmm<(+Booo}}},Mzʕ[nmhhx@ccc׽{FTa:v/V@=x`ho:%98tqqqqq111V'Mwވ'OTTTHNS b:c ={%&333++2ldddjjڧO޽{[XX4KV@r&&&&&&u}VT*=~8!!ѣ߿'ܼ_~u~O=~9I}]$YYYYYYSI D #9QZZz֭G122222rqquJMMM+++׻woImlKKK}veeNm@T*}Ç))))))111{ѡ+$;;;<Pɞ߸qrrr\]]Fյk:9d֬Yf"ݸqk׮uvvvqq3fIˆ(=zhpppeeDž F%bmӓuƍKe:t`:) ݲeÓs΍?UHɂ Appk.]MD?@A3***BCCO8y̙2 رcǎK 9s@ 8qqutL3F׬ IDATҖgdd4mڴ*իҥK###_|y|rCCáCnݺ533P9qO>СCK.}===qLQQΞ=… .]۷oֳgO ??m QL-@aԎ޿m۶=z,X:555&&KEEhmlժUnnn666Lgvr]]]9d}} &={ÇL((EAA5k>ӟ~+33VVVLh/qqq}=|p``ٳgҠ6qsνyɓ5553g֭OJJ Z6ի+Wjjjv_~yw!ϟnWd5557nrcǎϧg8$ yoݺܜ2p{2 e8E1!'^~u}.ZHCCC1n߾/YjՌ3eӉڗuHHHpp5k" 188G7n`:@PƣGF9yAرCOOPPXX8rݻw={v͢AEѣ[lٱcYd_tӧO \]]Oϯ\P5'NXZZ]~LTKK˛7o*p:gjjj?8s~~~O>]`o???0QS''yy{{?|ٙDҖ6p<5g߶~ϟ\r׮]=zزe 0jT```_zet"i{䉓SϞ=/^?33K.LgCZZZ+W^hO?ԻwSN2  g̙^^^wܱf:@`bbsEu >/,,,I[[{Æ F;w˗ Q]񖖖3A1COO/<<UQ.\8wYfeee1En8p௿ӧt(s(Ǯ]A~}_zzz2Efxxx.Z_CCCcwޭ8pҥK"ϝ;7k֬1cƜ>CLǑ%ί^wk 5E}:$$LJ8̫G5sLȰ3gzzzz{{}, gȑiii`ԨQ>>>L'YpQeei._:f۟>}gȼs8 ݥMSSSkjjm#Q,.##Hak׮߿ >r=--pSSS.+vDoǏuVkKLAv{?,,,ܹcgg{oLk)H$''ϙ3{<OWWo߾'O޷o_fff JoaaamN+++CCæ$&&xS~z\.իm]я?~<=hKKKB[3!$((㷂R,zjO2--6,--ᄄ %-O>𰴴VVVnnVgkƎۧOV&"}jkk7lp/_^SSӊHA555\.{Qyyy~~pުv.0zmvL555eeej333cc N| !}K0,jlhh8`Bo{ǧW^mU!ɇ7mTUUehhȶ/ɳIeƦMZZ2[q$.00PUUuҤI?IQZf !$ ѣG0b,9s=S׿gBA޽{߆CV/ɳIe򋺺ݺR޺:bbbtuuAqq$ۊwУG\_e01F+wy&!dРA3H1?~Lܹ^#G̙3gРA/_ B³gΞ=[ %?%&y6 ؘ "~ 6ۏ?l~yΎ( j\^:w\NXnGhh(=|p|@ ZN3Аɓ29[II۷.\ƚCؖ۷aaat2w\Bȑ#G ߆N81a>OOJg۲Ie:t_bccebd%[>͛#G,,,l=w{7CӧKttұc]VVVKu떨СCԩ+++>/bcc---;tE7puu%$&&/A8ph@ MHH`a6ufffϟWE!AAA6dv%~ZEE|V9Ε+WDs[5qɀ.0.suu6ͻuG266y}-YXAc,JKK{9v_޽SSVLJQԃ!VVVdߋ ;vga:֯_p&MkVmw2EQ!$%%E=,D("5цžlUUU{\\\,[r6#ݹs,nidku,[lugcs6I>0hРǏڵk׮] 6`N`O6.+VPRRG y:.\p8111M=Mq8'''SSSd^^JeSSSEs\ӧ9ս{666ay]7P;BHPP6d;y!ӓ6n(4q_2`OB]]׳scU}I_~QVVn l?B>\g~zzv ,'WQEEE^>|GG|~ކ D ,YBٵkWqqӧO L%%% OHHPSS6mZ^^^AA]SGDD]Vcvv6=а!\ˆ=ڝ~wpp())il)ˆ~?tT޽{~=xгBooo}}}L,#\r֭ ܹ#455y}'2DA?(((..n˖-Lg|aTSScǎCT[EEŋ/P1N[[{ɒ%;v(..f: 4`РA7nGwB(<<ٳg-b: ϯ166f:KVTT9r а˗2dUUULgh...)ЃZ0 :u4{l da'%%CoLgٳgW^a:l #!,[|2AafffׯKKKc: HlFҥ˸q LBիѣq>ZZZΛ7' FUUUǎ? YdCaaK.JMMe:4LYYȑ#Lg)ʕ+L*e}^z/^`: H FOvtt5+//WSSc:%K>}:?? ШUVX Z^reԩL%Eq8S5kRUUݺuk```TTYjaRQQ1qD|UUݻ,ШǏ=zɒ%Lg%љ3g\]]k-***:}4A);w|)=, ]h oNQYQ={\lٺu^~thG2YB&LtFlٲFGG3vZ55 Ўd0>|8RR>|`:4Lhjj[nLg"{Quuݙ {s}\a9oo={_ ^d0-,,3f AdVII Ӵi~w@S\?x=@ʕ+fffzb:Ҫ-++c:4@CCcڴi@rSLB& #\GhmmmB NOOOHH`:4/nb: =+?#777$"@<`C1ꊑFrI :3@&uڕby9s Ќ#{ѐ!C444 :wth̙39Ι3gͰx(**ёtɓ'jLn޼ym@[(///##GarOII'/0GsΝ;w4M{UBB;e: 122ھ};_ g:@ܹs׏ MAу~x777Ha #//;;;5(6SNNNbb"aKd ΪRQQa[ ]fuOٝ={(2ٳSNe:!/ɠd KYcI,1hW(P0Ba @ #!FB(P0Ba $h۶m5855]GGGKK%..NUMMMN=VVV۷oib޽ۿ.ի~<ӆ%.##J%!$<<Ԕ6 Nڰy(::Ғ|&V>o<.cǎҘ>}(++_xQAAAuv#dDV^M~iii@$SKKKmmmeeU["J믚___.w=z}ӦMUUUMFVȟl+$5kB̯=ztwtQLLLZZڔ)Sx<=GYYy999/_nzY3WIDAT33BHYYYQQQcm:t9VVV</33pZ_׮]r'OVWW7~W;|UF_ yՁCF4_7o?~ᇦ׏.kW,-n޼I4hLz222e?~LܹXZZÇ}r8Ux-#G̙3gРA/_ o;>m $:yȈf+ ӳW(jQ.kC-.***֭[gnnJ۰a=0nAio2==ݝ竫 鄐: !Oyde_ƶ"}555gΜ166vwwo6۷ !n_ZZZHHj+]鯰0==={{:k7o!>|hbC_6/I$j>E_ׯ{n<?~|///L|cњ-\^_)rrr:rݻwdff.]"ZCCcɒ%IIIwޝ9sf>۹syfTSSk,Xuuhhh7߈^rrrԩ>v؏?%BM>>{akk{Aрy y77.&P[[vvv,9WR@;XnGhhhMM !dÆ xdqDD=GOO|@ kQBBmHuuuPPknx<^~kkk]kC8ez˟slcȍRHSkܖ  )F3Fr3PZ2lJw1H#Rm3':]Ss{Ӟ>> !ӽ~G !meQQQZvp///pͿ=,,ͭi9;; hmms~?+z"/_B$Y933derO$...//{ƍ޷oh]`3rɤx鉋s;l·!00P1<Zv5___!Аbcc]]]KtbObz{{+=;vL1>>`=gHԆ\WW'rrr3glNNN1X,9[$ {dff,NNNݫD 322z{{BF0ڰaG5!!N+W m366i[޾}{`` <<\y!!!'OtҜ]===/^LIItD9i&J" F<[YYY]]]VuhhXxݻw egg/xСC?ؿhthܬ VQQ18>>^՚L{"qqq%%%.\X=G !oݺyf"""m/|ɲl64Mttt{{^WF䔔(? OOOF{>GyyRSS]]]׭[a4V뜲ooeBOѣGJ_ZZZnnSɿ%o"F*`"F*`"F*`"F*`"F*`"F*`"F*ܸqcpppd$i|Mww_AeygSSS=Ū7$''/ \10SSS)))=*!IAӆgT#@E0PT#@E0PT#@E0PT#:}$I$ !!!...Դg֭fTY5kFGGWUUɲ4m[#:z,o޽yyy9vΝ;}}}^j6޽[ZZ:::zW_}ujjjɣDGGX,/;vl=g#ĉ;w_֭[Hӧ?bNf??7xgpqq_y/RՖY֧oYX\EEFYO>sv9;;8qիO3N{-nnnn׬Y4͟C#nT$gff}FFF_u۶m^^^lj-((tk׮IJJoNRV-7nߪZv`hnnvhׯ !)NHHh4Fqffmnn޻weeCsBA0B:{짟~޽{Wӽ7nB˲a+NLLeY366v?###? !Z=^||ݻw|999kZdsSkkknj!Vk׮mٲ%!!A,b)**ܾ}{UUdvv}$I2...//{ƍ޷ohȲ<\~~jURm `X[ZZ8`2;h===qqqqww߱cmC;; !)޳gOcc-Occc۷$$$ɓ.]ŋ))):N1==ȑM6)W&&&٨|7kf琰!V.:44T\\,r||mݻqСǏwtt߿h4:4I~~~nnnVVVnnnoodEE` PʜKJJ'&&/\0Ҿ꫏>V999UUUz>==ݡ9!2+z[neffn޼Yr\,Fn{]?''D9((`0xzzj4؛7o˗/Z^^>`WWuEDDF:ѣGJ_ZZZnnS+5111---b|K/?d.`E$:%%eY%}ƭ4@E0PT#@E0PT#@E0PT#@E0PT#rL&$I=V!I=?, #F*`lD}tzPIENDB`anytree-2.12.1/docs/static/tree.png000066400000000000000000000740501452550712300171420ustar00rootroot00000000000000PNG  IHDR.[M0bKGD IDATxy\?שs6mJ*!DT6H ʾoi0Ƙ10c1C i2ڣhUdOE|2ӹNsuy1B!;!Bț…B!.Bi4B_uu5?~<1bACC"ֆ 455yG&wB !HII +W ==G^^[G]]%v=z} BC@_555©S֢UV5ڷoj hJJJ(--X,Fyy9 ݻv222 X Æ C˖-9!AT"gp(,,5\]] '''JUUUtTWW G,)!#*\矱cܽ{{ƨQ)&2:u Gc 1~x̜92B!@ !`˖-ظq#Ə9sk׮JKK~l۶ )))ӧV^ WWW!&#JZ m۶Ŷm_ѣGؾ}- ___$''㯿0ddffGQ0Tٳgacco+V۷駟BMMw߿?q9dggk׮Xr%*++yG#(*\*,\D׮]qU}onnnHJJ_M6w޸~:X@ !2r=8::bΝػw/> SSSޱޙP(… QB HNN;Դoĉ7Dih\B1d!!!ID"{oG~~>;ޱ!M.4 :...8x TTTxGjPś>&N]]],[w$BHC ! $77 |܄ PTTYf&M҄t4,$&&E#ŋ?")) B9yf,Zqqqb  /!̹H[~~> ,]Ta{EFFv;!;.HܹsqadffB]]w,X 33B7B4aΝX|9-|rPB߿9ɻyQFa׮]R7!DPB8pG6(oΝ; Vp}'x"dA !RR]]/ÃwC : }y戍mBO$DJRRRPQQ޽{KuAhh(BCC///ܹsiiiPRRš5kjddd+VٳW^E-0c l۶MA$ϟTMQ,4)9x P]] %%̬BRRQQQkXz5///?~,?%99ʰ{6ץNl>}AOQ4i)((Th֬ajj 󃻻;~glٲlo``˗Ν;Xv-֭[Ȃ>ww79X`Y?~L !QBX[[RSSdBsŋaee@Hߺu+1gΜ7͑7HdtڵAMQDž)177!Ο?/!eeeذaݻO1sLBMM .\իヮ]BYYH$H$m`رسg.]6mH-?>>mP;wӓB{…)~QFBBΟ?iӦBi)۵kKKKqsssX,Ftt4(/ Bhݺ5N:%8N ϟG^x!4~DžiSVVΝ;MF<ٳ1k,*Z!RCw\i _}V^p8::#SpssCyy9мysޑ!M5P0zhDGG#::VVV#DMM <==XXXXDi: @FFH 1p *Z!RG ! HUUGEǎ䄨(ޑLqq10=z#B *\i`-Z3g၁bhj-iiiիn޼hDip!D5k`,_}FǏcزe zCCC$$$Ɔw,BHF !2˗#""iiiܹ3mۆ:IJJ 郅 Ν1X& Bdo߾r My'Oݽ{2eee! B8둚 ccc :={Dhh(jkky{k׮aԩΝ;ݻw#::|hB !YZZ?Dbb"0zh/ݻwyCEEXZZ"""۶mB #1bPXX=z`ԨQ>|&{ SN 7o-Z%%Cf%D^`ذaHNNFxx8>|x1 '''zzzu̪*\v GTT\PWWW=#F?BBB;;;)bBykT"SN!""ݻw [[[899NNN֖d#p%Ɵ.ȃ  ((Ǐn#\rwǏ555(--X, ---UV011%ѱcGDx`jj*O!o Bx 5k1cZO<]]]ޑ!Y$m۶aժU pYbȐ!(++`p!P̙3k֬yyc8{,ܹC,DQ TAxx8&L???,[wֱcG;v QQQ'*++yG"4T"Eǎ'|+Wbpgjj'O"--5"H.HITTƍS" wamm'Oܹs6mc#B1*\txzzbРAغu+8rW^+WCih:B۷1p@t PVVI. :wƤI Dip!=c000@HHHYL8=PB;*))APWW3g@[[wFaa``DiDp!TWWc̘1E\\ yGjT֯_B3aaaӧHF BRmm-pEDFFm۶#5:;v@QQFhXZZEihT!o13fɓ8vu;R~ 0`ݻ;!…l2޽;xi7ocǎe˖ǏyG"9*\yC[n7|;vӓw&E8qb1gϞDcTۇyaOxirZng1bxG")*\Lŋ>277Ǚ3gɓ'w$B…HHHȑ#1~x]w&GAhh(̙;!DQBH=\!C v@ I! 88?V^;!D<.Ç1dXXX 88B!҈#uVE7oH9AƄKAA<<<$]xGRH… addcDT>|8ޑڊ+SL8-ZxG"pF !SSSQF֭[H $EDDΎw$BG9uuuƅ piXXXDG য়~B߾}1h ܸqw$BGTO?ő#Gp!tޝw/"BND eCMM G Bޑ!PBZ>|555/=m6Z AAA3f tmٳ(--Ň~GVVtY…4yƌCA|v5k`allgۘ0ab={o߾!M1x !988 )) JJJ֭Μ94 <SN֭[yG$ŋ߿?F_}@ @zz:x$HW.IKNN~aH$BVS >{xlN>ÇE"L۷sLGiTmҤI~P(*y#W[[ DFFoe͚5ãG)!WMdTX,Fee%qNѣR]޽C2BHC…4Y;vxwRVVٳ2NFӧO'Nۈbl޼uuu2NGiHTDǯN @]]ץY`6oF>|x'"5)$$>HGXX-ʕ+`D"DzSVVƦMdݚ4I6lxe1"vG޽9$$CWW7nDVV|||5ckkk ) ! ={ >DQQ={XRhkktttVZYfoW7$ UVO>yi^˗ȑ# /LJS|!//EEEFyy9uuuD"hkk|Yphpn߾Ddddʕ+v߿i_e˖hӦ `ee]gϞs%D"TUUc޼yh޼{6".\O?PVVtm֬rrrψb$''#-- W\AFF2337>H$!:uKKKX[[>k!o "=8v"""B(CF.]жm[j &&&Յ:B!455CQQHmwq<|JJJF~0`CUU066FMM D".\W~h1,^wޕ,`˗qqDEEx455ѥKthݺ5LLL`hh]]]D"4oByy9b1 "''7nܐ%%%PSSC޽ѯ_?|ᇴ8! ~ib1vڅ+V022BCC###ΰzS͛7(DFFh۶-<== [[[@ y7.\@PP`ذaĠA.,=Ÿ;w***6m4 /ػw/VZm۶7n<==ѳgOIa!99!!!8|0nܸᣏ>&LBL!o!&& :`lӦMw,ln:֦M&7XUU_9SVVfѣw4D˚7o غuXYYXȻp!oի suue#Vuu5YvH$bseEEEcf&&&LUU͞==xwa .djjjȈٳG ,B .ʘ?DΎEEEVَ;X˖-YV޽{yG" ƍL SVrss٬Y2۷/KOOy#M@Guecظq#;[D>}:nܸ#Gƍ^"MΝ;ѽ{wIƼcCCClݺ/^DMM e˖z"DQQB^駟~BϞ=˗/cPVV ((aaa-y"塚&L/̙/6w8,] ,'JKKy"DnPB^E!""cIM;_~8s H=y믿p[L+W""" pqqAnn.XM$cÞ={cL/]={bXbB!JKK}vI;|II 6lcO- IDATaÆAGGLLL^ZJiiiRy666祲?*++"kSYӧ_O؄pûaggώ;2VWWclܸq,//1X_,,,6|ƶmԘKKKc|_OYYz%bcc>s폼۷o3,11Qjl@}\cƍ KMMm"~ ԓ'O'}>}O>?sbŊPUUW[JVq/X6l3TՆNe9E@ME ͛7> Ϟ=ȑ#W^EΝEEE/l6y֭5K3폼 ש,=ƀ #v1c 55 @RRgϞڧ!GgXYY=z6999}ױPw{>zLjC_TXXՕq |sʕ077Ǚ3go>b,_5TUU{ 糏́СC-Z 2<<"H DS"i^ }ߋLKOT((+++Hu}1c%YJy3Ϛ5k͛7K>Μ9Zɇo1lܸÇɓKb(--ȏ;v`E﫤YYYڵTGޝ,}6uOZ$4o۷o"h믿EEEh֬T)`kkq!== DvpMx{{###}-[0uTm~!<==~ر#7o ̘1oHi7oĀ0m4M{"77W@6lpO)DDD`駟  W_a֭T^L>7oDTTIաѮ];`Ĉȕ3g"66鼣6mB@@rrrޡ1055̙3rJqZZQiصk(r2e (P^^Ç"W?"KEѝ:u C ӧ1p@qZp!~ܺuŐCSN_4hkkMYYw"kE <1&M\q9s 6oތ͛7S"֭[Xӧ󑟟 6BTlݺ8qTi,n߾I&?78-[/ݻk.޽["/p!hѢ:K.uuu#L^^SSSC;+,XG;___,[0 Ehh(9ӧC,=z}HZjkkى'Ù2SWWgcƌac2˖6nȜfsa2@[QQ d kӦ ?>dUUU2PSSbccg}ڷoΝ;=yy56vXT)&D.H,z1ư~z,[ SNŖ-[v EHH"##!aee~W^%5k^y>} \|qqqFvv61l05 D8JNNFHH9WBEE}􁳳3`mmm۾wʕ+HJJBLL PQQN:aԨQ5j.\> =z4[O2ZZee%|}}~YosEEEAtt4bbbjBofff022B֭mmmhkkC,b%%%CNNqdgg{GGG899D MЦM駟b@VV@CC:u1Zn ###hjjjh֬PQQJTTTƣGpMɨvڡo߾ bbb0p!,Y;w.-[--9!QB^#9YYY8x Ÿy&\WÇbEEECqq1PMMMUV011)v KKKZ\Rt~!o())իW7oJ\ UUU(//G͡ 555hjjJVZcǎZhx"z`;^Wuu5P˗c vLBd x5 8z(,,,dr\;;; 0_LGg}ݻwƍח1M'N뒂bؼy3̰z- H |.^(Yz*~]VfE _hcbݺuq0~x8::"..MHC…1|G>}:? eΜ9idzD`` RRRdr6m_EBBTTTq29>! WZZ OOO[{͛i!7d۷QQQغu+|ʔ)prrdv\{{{DFF",, ׮]C.]|e DZpQ`YYYի.^(DH)--ŋ1m4ٓK@-[ 55?̏J}7yB. *::{ 㹽"+_~%***W_qaee 9992?P(/nݺYرcLpQ@;v쀻;\]]GËIǫCn}#5uuu4hf͚={"**[&B. D,cܹ1c-[`EH!>jjjظq#݋pY}v\|Z <<<5!EA|8fΜ*q`iicǎٳ(((@\y. ==u;!2!r_'00ذa(HJJ¾}:`ɒ%(--T4y'OD߾}allDtޝw$BdF^:֧M6X|9֬Y#Ws())aرr VXm۶sرcjkky# &1oÆ Ä p9E[|gر#fϞ;K,x{{cܹڵ+ш¥ /_kbPQQ غu+Μ9#GJzzzXnammK!D֨pib=zggg8qO:ܒ^Cn}I&ax8ԩ<ϣ =zqp]ш¥ G=P\\ϣ#"s!>6l@EELa|_zBLL BCC ̟?ż@K~Zٙaڵؼy3RSSyy#Æ õk`oݤ¥{L^c[SA0H___\~ӦMC@@/cw<Q҈bȑ3!3wF!>JJJضmRRRk.qފ֭[7o&L@޽;ibpi|%Zٙ!722[li|׮]1w\#??wfjj۷ŋPSS3 F*\!ZٙCn^xyo9T+;;;ٳ>,--ǏF9*\+;΄Oc[MMMlڴ {Add$8|/?3;sssw4HQH{e΄wȭѣbΜ9())aҤI˱qFtԉ F+;΄Cc:[lݻwqFQB]]]Q0{l888 <sssl޼bw<"Gp#pvvɓ'iegB^)wȭw}RYw#++ SNd B*\Lțirchh 6lPN%1~x8::ΨpWv y E[???gV ۴i_D۷/ƍ۷oF8…Z,YVv& )J())a툏/;L988 ** aaaz*t磨w4"cTpRZZQFI޼y3tRE[̚5 } xǑK0777|Jш'%3!oO;g͚5PUUŲexGB$nܹs_4)2,S3f *"aaaXx kܻw/|VWWDZcвeK1 'W^ ۷ވSȾ-rxyy!&&/tD"Y{AYYWI 55zP(|6555qƣW^App0aee%K*\RUUfΜֻwb˖-puu听qwsQMM >#/.]ʬYp)Z3gME ;׮]ƍk.\NN/ôDۀF/|a?)++3]]]v}11@ :ޱݹsOB޽w<}v& _t5BTUUYN_~nݺgϞqHhq=]%%%2ddq277-D"L@;K.pE"455q9L<_F~2sy :::Xnn޼ L0/4]|{ddd`ԨQ$M 0cƌ* (U7i$7M0såK$B:t@JJ 9'k>> ?D_^ݫKԠxFII jkk$iVSSڑ o*44O~6"555h߾=\\\M?aQQH755Ž{ PMMMe˖Kuu 粼=r~xTWWCII C N ..>>>n_XX|<{ O>TVVJkjjB(B$ACCzzzhٲ%ezxѣ"""pQs/͙S[[m۶XvL)scXv-VX@@<|Pr=zWN\&7o}}}CΝ;ʪINQ]]N:Çmڴ ϗ1KKK7ow/[RVVX")) IIIY3 $Lssshٲ%мysQWW'y3Dqqa^^pmɉw|;;;t}􁳳K -[ ~"PRR@ L#FHNcRSSDGG#99IIIZh!9۷1ahh(9ZZZ.J؟'O ??_r.޽,ܾ}wAUUB!,--ѽ{w~-Raa!"##q$''#99YrGu IDAT۷9ڵkVZ@rGMKJJ$0??999sy-dggK~ݻwG^ꊖ-[42uuuHKKCdd$7o155>ѲeKZZZ;d{|+((Ǐ%yQ[SSUUUpuu}/nϞ=O^$$pA3󗙙:Mϟl $;v ΝCBBjjjйsgIuM֐JKK&)p5(++nnn6l`ccX,i ޽;>|Gmƍ EDDbccQVVSSSۣ{ YYY)99.]BII ZjWWWcĈk0v1V]];"999~`NNNLIIihhaÆ74N;^ddO$H"!XJ(mB-^KroJVۭR[-EJKQAVA H}I%U]F988rwwKRvvء vM}%DBF流 ëbڿ?!iiiȑ#ѣjtRrtt$A})))W_Q׮]I"5͙3Mpr=J#G$MMMӣ֭[@,;;nJ&MMM266'vh/tR1ǐ LFVVV98J%==fϞMKƍ?C]… 4qD'===zbt4|211%_UU%vhVXXH[lΝ;+ ^ |21R)YYYќ9sbIII'P˖-ICCbterss#ԭ[7Hˤŋɉ$ ۗN8!vX/GgϞofΜI{& nnnfΟr)pȠ3g.Њ+(77WM79yyyzj'mmm2eJ(`iΜ9dhhH222K0W\Ǔ&999ѦMMsE &DBk.*++;,ATTTPDD,~,UTTЦMёhҤIt5DUU:tzEgϞ-vXKO 6OݻwW 3J?j\R^^NV"###YTuQVVF6l GGGקŋSiia[EEYLMMʊ-[V.+))yzz222h񤡡A;w6È9B=z DBcƌRDGGSIKK&OLb2N} C7~&7PH*_ gϒPxx8)30QZZJ-"===rssS_ ϟ'///Ң9sV˳n߾M&4j(;:f266&{{{ڵk!j߾}Lb 9͙Mcƌ!lN5N[&===Zt)UVV+qRUUE .$LF x-EJJ :R)}'MCUU-^455o߾t-Cj2}t255ULD'???߿?޽[L~~~,\N4d*am*++7|mnn.YXXмyiihh4>$"zmٲFIo+WU.#""H" 5k֐b=)3bWmFRT%sQ5Uk. "ׯԩSiɒ%4k,%&&ЩS /O_b\rppJMZZbj4c @˗/5\|$ >|XvA@.]Rf/osm-ZDFFF*;{Q6m ue@mڴy嶪̥Oyy9988>G1Ժuk4i~됿,իйsfUUdjjJ&LPj _ѣ) @9)Q6m^ddx/\Sry+//'rrrR~ӿ}jbl޼ Hj}& /X#GDTTJJJ룤:Oz*\]]1{l$$$Âhll7|#G@D>|}5epp0tttСCܹ3GLbWG*;vL柿`ܹ033u^zaȑJ!{p?(5;wiӦ!<<2 ظq#$???+Wqn߾`SN9s`ggWc{{{{Ov/g<|&Lhk}TN:RJo)pݺu9s&&N\R6ĉ'PZZ*h?GA@@ZheC::::ի"##'22R}*44&&&ܹ9l~0>kĈxeuӠ={ f֭CADqlԨQa;}Շf̘AG 6ihhիWۛPqqqv@IȈƍG.P-hƍtիW m7=9/;qD͛GJ}Ϻ|2|P`n;F\> 8U$V.͛Gނ_\\L)QԩSW^:cǎ@ꙿǸ,[T5sss@_}UUUQ||b&={_"4vX޽;xj?<___}ŅƍI'O&mmm5kseoߞk޼y#HM)DOƾ<ŖN\ @'OWWWZh m gp+k֬!KKKOJJ"tAoJyk_ mܹ34e:gRXX3g΄? addTv~ΰa7n܀; //=lٲ2 h۶-cXj/_^Jq4E7ߠCç666(++O?;244A<eAA`~&K`9|HHHwP=WXXX ;;[AU +Wп\t ݻwߨ6ZBviii5IOOtUhݷo_z,XZZ ־>|(HM){?OQQYY5k4*̬P9!v.̣y|BS_pܹFPsQ^^O? "##sNTVVbDzYY@."za <aaa066~nPǡwy1fddlllF.###ւ<=--[C5VUUa;vs텄7nWXjj*: t.J\ GsssHqei*y|CBCCիWu6zzٓG󢣣yyIȈhذaϟOnݢUV)&ߩ bu\NSL!C(6-](???~L5Vӭ/aoo_c6nj  MMM:_?џI(..N36n۶_ }JLBgmm-19;; Ҷ*rYzpsqK'ر`S7<_uvvt됿7nPVɉKDEEQ`` 3O@N׿:В%Kwޡ {.ݼyIOOG7oޤnݺQhh(TZZJGSϞ=iҤI4}tZvm_ŇҼy($$6mڤH?L#F 7ߤrwwѣGS||_{ _xrlllh…Jo)p޽diiInݺb ___@:::tRݻw+g:uJTK"Ǐ7@2K \s3ϟ/lM!bW+E7UϧKҠAёڵkG4o޼NLX _ܹsFB^7L}M:\\\x%a\~444hUQQAւ`O~H*uCrJ)_աNJ*[H+,,$ggZJXX9;;SAAlΟ?ORvܩ> ܼ-ΝKFFFB&zL6,,,P8pyQXXXc4e233tE4ydٜRvo߾}֎;T &&R)mڴIʊ,**$JDp{qBDtH$uֆGh޽*U۵kI$ڵknNr9M0޽{Bvvvdeff 0@hnn.СCyY#= >\r{yBrLMMMlHӧOM6Mf̘AtqbPw$[.rss]vɧp:vH!J  vymsc&wwwQ}qG {u"iرdhh_|C&&&4|pQ*zؘN:%Zjɒ%$H;wQ@@<ݻ7YYYѝ;wD_~!TJsQQu#޽;lْE0J߫ '=lhg} 2I\V^VVF!!!ͧꨲNJRTռ+!!]1{lٲsŰm6ԤqQyy4yߧۓ]vMp8խp!z?$ W̪j%KT*M| wޔ*v8Mɓ'Ύڵk'pF{RmÆ K]tЬS׬Çݝd2ҍ7I%n߾M&L MMMrvvVC矓.ЪUHBӦM#jժٳG'x_T;{, 8Pǎkv*))~ӇN8!vXJs%z뭷H"'[NmNǝ;whܹdnnNS3 oNAÆ Çlm%Ut19r$IRrssٜNOOٳg>YZZEX(gϞcǒ988кux-Ο`WTp-Z|@.\P_𱱱4{l233#MMM t uIOOiĉtĉ&uT}Џ?H# ?Y/\XUUE{졞={D"!'''Zps4VRR-]\]]Gzwܩ/E'kkkJD{U룃f"K[li6E8J¥ZVV}Ff͚EOnVVVRLL }GLɉ-ZԬ~J^^] YYY?TsYYYuV:t(T*AѯEJIDATl^$!!f͚E|||h?*]F/&???@&&&S\\ءLyy9ٳGQ|ӨQh׮]j1rRR^zAd``@'NlW)OiI ˗/޽{qMgϞ۷/z OOOd2xJ$$$ɓ‰'1!!!ԩh57o޽{w^BGG]tA`` z:@WWWq9?~ǏG\\455ѧO1C1 'N޽{믿"==VVVݻ7ѣGACCCwӧLOO #F 00( /qI@HSSS ** QQQHLL1b 0@}X8^%n޼x#N8\ ~~~񁫫+\]]agg(HLL۷qUի(..F-гgO}􁇇nN߿(ŗJjj*d2:M6pqq#~~>"66݃D"'ѧOS@S%3gPTTCCC^^^puu lmmCzzX\|E.];;B**=uh?~QQQu$ \]]___ipvvRq\vMȳL&CN9ҥ ڿ՛ rܸq.]RYqqqhkk VVV%͡cccQ^^"O؊"33YYY{.JKKh߾clw+WJ-annkkkR000C(..Çldee޽{5vppP~~~QPշׯXXX 077󨯯---TTTcqq"x!޽[v)ءCx{{+}':HKKŋK.ʕ+x@"vvvYYYd~KJJ9Lfdd޽{hjjյgjJ./;wΝ;{~KKK )**Byyydh:::5kaa''' VVVbf/??wQIOOW|hPPPBTTT(:[hD===([VWWW899)h{^ZZ"IIIV"%%%PZXX5H$1_fVTTU})򗙙|9Σ"o854…1c cɒ%b"c1'.\c16pa1Ƙ…1cj c .\c16pa1Ƙ…1cj c .\c16pa1Ƙ…1cj c .\c16pa1Ƙ…1cj c .\c16pa1Ƙ…1cj c .\c16pa1Ƙ…1cj c .\c16pa1Ƙ…1cj c .\c16pa1Ƙ…1cjCBD$v1#G࣏>BUU⾔\q>~7XXX2c1 W^}ddd( U&>Uc~d2 2*JX\0cjJ&a̘1|6UUUxwUpa1ؘ1cPQQ1`F$,.\c15ֵkWlٲ455oC[[[Q cLI$zQ cLͽt9z)BD…1Sshݺu455T*RT…1k=]TQQ1cƈ0x\cHLL푒D"rdJ0c̀ :tDMMM?-Tc1l;DG9aZE1Ƙ),,DNNrssQ\\1' 022LMMaffc ۷qM )) HIIAff&rssQVV֨>aff;;;888prrjn=.1ƘH***peիQZZ ȉ GP.**Byy9'Gh>Jl^^^h߾=|}}ѥK. .1ƘTVV?cp\p077XB۶mrrrk׮!..Nq+))tݻcpwwWilÅ c1&Ǐ8p"###{֭t"VP'tΞ=ӧO̙3Ɂ 0x`2Jrc)[UUm6DDDo Cl0\˗/~Á  ,, ݺu{.\c1eIKKڵkyfG EHH⊟ݻؾ};v؁[nӧOGXXpa1իXbv 333L2ƍCVMΝ;͛7cǎ000ɓ1}tXZZ* .\csñk.xyy?Ę1c%vh~za֬Y={64.1X}O>~ WWW,\oVb1_/L&_|ɓ'CCsc1er,^cǎT*;&-77K.W_}l޼WR"1X]!44CE~pu?:055Ųe:tիUcBbb"z-dddǀI-y{{Xd fϞ/bӦM0c/qt ZZZx"-$СC޽;|.\c8{, ^zӯBz7q|GLLsكm۶!55с=C\RT?6l@bb"LMMkۄ'c1ƪ쌟Ypj())-=z|u߻whӦ =|FcC"@.cΝ1c -[rjUXXWWW7_~em"cÇq,X@P[ _t\.СCqU?;wV,UPݻEEEJ> 0w\]scODD:w CLDDbbb0w\պM^0rHGDXXqZ…1~ C?;wƴiLlܸDq#??+Wqn߾`SNs >}t#F(}-`Μ9 CW> ]vk}Ǹ0crrr`nn( G֭H$Xf ,--₻w}y&f̘ ܽ{fBii)._ //I$Ƹt /^D^^r9QQQ ڵkJxWGĉp³絊c1<Zl)X=£G_c@SSk,X###@ii)fΜ+Wb֭zQ:.SLfffyfxA"c @AA( !_9s&QXXؠ~ΰa7nܨs$$$ԹYf!88| -ZW^ϯ1.\c1  +Wп\t ݻwߨ6*gUMsܹ:qoAtgqc)"D ǧ~ DFFbΝ@1 ˖g}px\BCCիW#--mJKKkTcǎEEEUq5ƽ{`kk[c\0cx2O;&X+VG<9bddvww,\oիELdd$Mnn.' 2^UUU5Jؾ};tttЭ[7DDD(N R<9GbǎH^TTvZc|Uc1?_|6oތ{zrcI$tFB||<?~5k nBXX]]bڵ8q"z N_AWWmڴɓn ::?#[d2,Zb[N7L ̚5/| ͛6m믿矣G^8LC ΝCΝ}g1ƪ_h׮~'&`{M0N۷k+pa1ƞ .xUy&<==uV;m… c1wW e«D>}K.ACa"c14ggg,[ }uYM}.\/*Z\cV!!!8s N<6mڈNsNaƍ4i6#.1Xmu4[?#Ǝٳgh0c244ÇѪU+ݻX#_aaalٲ:= c oooŜ%0i$ϱ|:? c%p̚5 &M¸q3ײC.]?c߾}r c444xb8z(ڵk{Z)++Cxx8:v]]]u^_i\0cu4d$$$`Ȑ!9r$뇋/VFDسg<==j*ɓ'֠pa17nĉ'PXX۸qء5)DCGF׮]9sXWpa1GADD]OOO 8G>>4h .d\0cv E1p$%%lmm ///mNNNptt`GgJJJdܽ{׮]õk|HRx{{[n .1Ƙ8#>>JKK2 hٲ%LMMaffikk ===Ǐ!!c ''Gq{!Ґڵ'Ѿ}{oSjDž c1Tedd 99HIIAffENNc@ii)JJJ}6/_͛7cƌG}VZŶm۰adffbذaXh\\\XG8}/egC1rHbٳ'H*q= {A=XG8]už}Я_?xyy… z.\@Nзo_8pu$9뀭[b?~< +++֑Tǘ1c0tP믬#qNk}aɘ;w./^:ZD"lܸ֘4i:>gΞ=L0?#85uTl߾֭8ECڪ;w uzKDl߾6l 8aÆ ضmX8ƧY$ N::J%%%ʥq5tf6 òeTcǎaԨQ7n\zLL  @۷6diӦ*".]$''k|l|\|8{,.]mVUU(**3{{{dgg޾mTVVe˖(..fJN닟~Ics{D.#""&LPv<`ynfllt~ܸqؽ{7  x3"/_FII qE"Qj}iiipVWjXXX͍u<|Cyy9lll QQQQ9<ŋ!P]]Txxx`*_rfffJq7s-gggd2cǎشinݺoV2t FW_}χۇ"Ю];dddlO4),,drȓ6l؀$̜9Ю];899)7ƍ:u1̛7.]\͚5ksSfE>|cccl[,?L&D"Qرcmz޿?1@\XYYX-nӦ >޽{ `HֈGvXG8›ӧo#((fBee%X3gSN1q4*~Tm߾}lll?୷bDq33ΝƍsNHR`ذaxѪU+dġC066ᅬSK.q T_eggo߾pppٳgQ^^m={Dǎ33W iiit;w8w9qWaĈ8s RRR`mm ::III@ @6m١y水@ %G JJJp}ݻ@FF?z=z#GDll,Ο?-[2 -ъ+_ĉu"?T!++ Aaa!\_͢ê "B||SNyܹs#G9%iqI 0k֬G}:Z9uݻw兾}bΝ_a^ oڮ(((@rr+H9n֬Y6FfϞPq:kݻw1fq4q/Ohs'NĪUXa]vEq9Ԁ7smT^^ݻэ$cܹꫯX8m̵Qhh( gϞFk֬… w^q8Nk񋆴̚5kc9rh"=f"gϞ?,X?uWX9smQTT///xxx ؓ233ѵkW;v` 3 Fl۶7pttΝ;ί'KDGG?S?_aǧY;tqFL׮]CRRo/۶mСCWX5~?3n WWWqt_a,ve| q_aF8~8b>˥*|E i a„ (..Ǝ;x#WMb݈EX8\.^:ؿ?aoo +,rfQ!"=oax`ٲe={6o?\gl7[qfQg"77xw{aĈ8p f͚:bn:oCEEEݻw}* ͚5 ׯGuu5@$x"'l<⯿0Z D333F9}fQ"B˖-SqX ӧtSTTgggܼyr ~qBS>͢*gϞ@&wj5,,,pme#ݵkdzHDDS?r( ƌdӦMУGΝ˯,RD5k(WYYd\t HKKí[^}ddd򂛛s(p f~%;v qqqHLLDnn.B!Zn m۶=7oKKKA$)KKK!QUU#''yyysq5dffBPEѣ +W{wŴip!@~pB>|OFrr2R)M-Z=lmmamm PTCHRTUUCNNp!==h޼9z@ёqE8=[\.رcػw/9w={/z///SÇ8<xUVx뭷0d׏AG,^K,AUU]v@޽ѻwo7YL/"&&шFii)taÆaܸq|yde˗/ٳE${_Srr2rfr9%%%W_}Eݺu#@@4gJKKc{2Z~=yzzrww!Cŋ5E*ґ#Ghʔ)ԬY34d:vƳp:/\yll, 4jՊc=UFF}7LgϞtR(5zeeezj####1bEEE$J)<l, ,j֑B˗D"!JMMe) ھ};Q֭)..u$N;o橩I&&&?0=nr֬YCM4;jPTTD DB+VЩ鹆ܿ~mŴb q8öݻϏn߾2Fݼy|||ܜ:$wwwjժ^3+ Zr%D" %L:=5oM6QN9HR|8԰i6|…$hڵ,*V"@@˖-cEgݻwۻ$&Mĉf*{%o˗/'PH7nZk$gETTTPNՕ߿:F:tHy@k4>LB֬Yauw}G"MFVVVtQذa B:u([[%##;wFpp0nݪ!uرcqa\p/"##1x`ٳÆ c#F 11/_~|9q-?FFFRTVV ...طo8Znnnѣ~7q*.. Ə+VñG#w:p?#F@ @ Sܸ8 sE߾}1tP,\uP(j*UV*fTT|"00]vEpp0~gHRSN ֯_: NJg%&&lPPPS{1ngΜQy_E(==]۽{.6m(~rrr"ggg^`dkk((\{[nw﮲m>/<7nyNNNT6:u–-[XGJv킏\]]Uݖ-[ B̙3(//QYYqUe„ ׉Oꩽ8q R˶g̘"š5k|mӦM U˸0h >>{ 8v򱧽ǔ)SbiӦsԒ j69si@*~-˗+sNoŅ{DDDdaaATUUEqqqʹKRTTM4w4fꎛ7o&ts-\\\u;;;Vi#BAYYYdbbBhtھ}; ooo_d 9;;eۜVS{!P7b211!偟SQQ=\vADr999X,V~C V\IիjjjhTB!EDDtѣ}|L,c„ *|Eۮ!***Tm]ࣱǮf IDATF^^|yGՒԔg)Ι7mj??}tZ ׯӟ=III􄓓ϟSSg~O^^{=|h֬%I|^Q]WCM߿qR)>sv^}NWֈk\:D$iFe}5//3g"22~a>YYYYyNNJKK|xeoo*@yyrokܹ~aذaE~~>~'>8{,$I_Lܣ_FFFpU̘18t$k>""KYYֶT!%%*&#=+CSNUG "oljс!C(RtĈtIv֭[Gԭ[7JHHիW%ۗO>RRRw%0`YZZ=}TRR|@={Tvt4qD{޽iDCuQYYY{(88k-ZD%%%j*caaaCBA|IK.Zk׮EVVV+븧BV0c |hc֭sZh`,8yd}PAAZ@tyqt\.ѣGY BٳgH${i.ې .#999ѥKX`ŋԦMjӦ ߬jzАv:HR5jܿkDDyyyOM4իW+or9}dllLt}֑\.Yf@ 0;wח)**uNmD,X@zU&==zI_SMM Hz_~!ѣݼyuسgYYY;i/RΝI"Ї~Hyyy#Lnn.X,&///>W\N:1-\XGz)oV.,7e.iDD2~gjٲ%QXXegg޽KԔh͍b*I[TWWӊ+Ԕij:SXX:uu$NiW3-[Fvvvd``@!!!:scǒD"!{{{Z|9UTTheeeQHHbrqq-[hU4g255%KKKH$i&ֱ8*_Syŋӭ[XGƍpB |;vH[l!T:ݸqƏOdmmM3fЊj DdggG}҂ H(رcnLӧO͛ruuٳgӉ'4k,rqq!dkkK~)?g\ҥKm۶_|A)--SHHY[[@ }RxxxHΝ;k 46d28|0\X;]t\\\`ii1q5\rϟGBB._ Lwww[x뭷XwTHP`ȑݻw ^^^Pޚed2ܸqHLLDll,.\B :Cm֊7oİap]x7_)w\3RVV㑐gŋe͛666033wz/++CYY򐓓|ܾ}[yCjccct =z􀏏|}}Usk.5 wƈ#h̙3w:tZjXXXM4! PVVRdee!//~:!kw ]_UU>[l,]BFWഗ7') dddڵkHOOGff&ӑ[[[TVVD%abb[[[ۣyhݺ5\]]֭[=q5t 'O_s qe͛Evv6PZZ T TWWfff0771qDnnn*uM'o߾`mmt5,]֭Siq[yy9wsssDGG@cǣgϞv:t?Ç\.GDDU>S4.K`&MBAA"""8yZߥK$''q8ݡ\&!>>zbWFDD~7lRmbԩSjYf8rΝiӦaܸqx㴛7 .p%&&",, K,A>^`` N> udD"|Wؿ?<???ܾ}[mqKyLL 5k777Q81|p 0aaaO>Gjj4h ѭ[7=zTcrEyll,-1BcB,c֭{/hڴ͟쌳gw[osBPhdl=nD3gFn޼yMjl\PΛ? 6l؀UVaРA(**;zSSSQXXbX~=tϛr;e#==:4:>yzcbb`nnN:1cԨQ8q" }pO_l\A$iXUU -[bƍrNcO/6zϜ9çX?nŸ&M0!y'mׯ_ǽ{x3o~7l޼7oFvXA`` bbbP]]4?}Qm3A&MعtNsbȐ!x4oC8O__zccc㣶78SRRC .dG[f:oE<::_ވ&M رCn51~~f >_ވ,_@xx8Zh:NGee%(^6ӧOݻwgӀӧOcXb~۷/R)YGۛ)ifbԨQx1c qe˖h߾M}]<鋺Ky^^_9 ùs q)00(--eAEݤw<&&B>>>pj~Z ?^uqK`` r9Μ9:3u^6Ν;Ğrnܸ"$$ufccwww7?}Qwe3#G {q^.̛?#557s=Lڵ 㼰@\xN_Զs;j汱 "©O?۷~C۶mYy)eHٓE{xxh`f$''c̙/1p@q^%:wSO4hZFytt4bCxwy汎unIE7ͼ.\BѣGC&a׮]zqר@\r~vћfLƛYh?]vYf㨄?$NO?}Q;M3E`oo: "'NEf2&&&֭4s KVV}9㨜.o,EKR$'')=QSSظq#8j[n!##u/sͼCg}H#11UUU|\O̜97 333quZ~"c  B@ʊ)77uDܹv}{O~)@ ͛724qDFru$}s\PD"!H$ڴiCSL"qpU2333g6tQ #'''$@s믿r7n$8p DD>{{{4Xׯel߿?{077Gtt4 &Tw}'d2 P]]]=7xC>|8r9""":>٣ssyH$_|JKKڵksB.&MDDDe#CCC}Z#-j2drJ?bڵkGUUUcrX`rL$Q^h…$ رcUqq1*<ݻwYGU+LF , PHcǎtoΜ裏>w1..uD)ڷo_X M:u4l JcjDdd$YZZRΝ֭[pfiѢºb1fΜWW ꓴ4ܸqc555_,_{p^ȠA0zhH$znbbLO/6sLV1P[[[|׌RqϲgϞz\.B`0H9֭u;$zKÇ R֟ ^Ƒ#Gxs)6ljoooјy'@@B̙0V9/֙w8q"X\\&.Ãoq5b̘1u 2u,Ν;GmڴVZٳgƍdjj>>k`ȑ8~8ޤI\zZzsI٣SϜ9CdooO]v4i$_)##CPrr2\‚uڕ-[F*SiK% i۶moH$"HT6 P6YUyYY_<== uؑkt钪z&TJG)SPfH,Ӑ!C"^xU.hǎDԨ믺f^VVFW&;;;222#FPTT6ʤR)S~H Pǎ)<< h*fܻw֮]K>>>$H,%K4z3?RӦMܜ͛w9<@ oooJJJbNvv6^ڴiCȨ1՚[n$H>">)%%H(ҴiӨu?[hh(ݻwu\3W(|rH$O)BA۷o';;;jݺN?[zf^TTD DB+Vй9>$iŊ<?[l?Ջ5LrwwVZ<[C \D"L&c^xo77nyzz= K4|paxYg߻w[jҤ M8G9^^Mg%f^QQA:u"WWW!"DB ,`ןן^^nӦM#+++s+E6l PHNb2>??Kl@n @@* N?[l=g̥R)k׎Ft:7oћ?[l=gWZEƔt:~ ###}ן-^7s\N4k,9y$ >\ٔ)S̙3 ~7}g*_R)9::jdDoH\\}}]7oW -UWWuuYZnMꅾO{NX; l=7 & oWSܹC/7Ņ[秦Duc_{'X_4 [3Μ9qtwŠAp}?[Ϫf^PP[nG#+~:0;z¥K@Dؿ?L#$$M6Ν;|HHHP6u˗/ǤIеkW/_lݺW\Ann.ML^Oc0g̝;f€0k֬Z7ngCΝ#tmLsNdddy3999Quu5YXX;) "@/;w y{{5,YUSgRɓ'ך߿?Ѓʹտk{>^Rj߾}.}Զm[P_wѣ@-_Pyʕ+DbX:GQ(dccC*|ȲR6Moȓo:7}'224s}}\[΍.mFhΜ9. ?\zee%>@ جYP^^ףR2#`ee|f355EEEJoPHNNҘxRczo||<l4T:sVVVPk~HSQSSSג '''̟?NHaa!ն}D/,,?SG.k2V-XОz}BᣖxΝZϱXXX0huyӦM@g)?~lܸq B}t}eA///ǃoӦ]]]yʕ+Xnc}Cu^}=C,@~gCi.\Bܻwl_IDATPVVB 豑B!䠴QQQGII G{1 c@"uS%۪_"??W\AaaaRmbѢEF>}$DDDڵkdffQYӇ7Tw@{?ADD֮]qơE1r36ԥ7C*?q+P@@P֭[Ν|ʕ+n:277nݺQBB^,--)88k-ZD%%%j*caaaW~ i͚5*өOvZrtt>}z?DVVVdkkK|+>w\تvRg]i,/--9sPi֬Y4gZpaDϨWΛ7>}˗:xgߺu9rDtرc[nj׿~l?} ޽{ӀԓJeee1mܸQ#?[Q7hEutM6[]׿6^xz7|۸ rwwJէ! $踼sֿfIVVV'6)++oA Bc ԿfNDsNk.%rJvvv$??+l`̉f̘AtWOCBCCȈbbbgן5s\NG&333:y% fϞM":?cl?kDU~Аvr)uT*QF4ן-^x9ѣߐf"@@aaajݹs|}}ܜXǩן-^xb_~LLLGt͗لٳgYYY; ^x5s"+WPNظ"44h @SLQ"E?[u|3'z4b 255%gggھ},OaaadllLnnnt)֑^?[lj, !XL...ej+33̙CdccCV7^x'"U5nܸAǏ'3fh[uu5EFFRPPD"***XGS)^xjWm3,77.]Jm۶%J_|%''kcPii)߿BBBښۗI*j$+lH. Rߍ b޽Ȁ///xxxƑdqRSSX\p >>>:t(Z^ן-^Y?)55ш3gF`ggVZ044D&M`hh2d2YYYCff&_jbkݻ7ﯼ6?[z^6'HKK͛7l塴R)$ `nn-[puu;`hh^xҳmqJNqǽ:9oqz@ `qo"p_2IENDB`anytree-2.12.1/docs/static/uniquedotexporter2.png000066400000000000000000000471741452550712300221020ustar00rootroot00000000000000PNG  IHDR[*bKGD IDATxg\g7,,U:(Ui*QX@b{DѨɝމI X@EE"R^T;۟?A.^s8?fgf!|d"] !Jt`iii)--ohh@t*Iш!-999###'''33/355533333633#H®IDS^ZZZ#""⒓l"&&&:::jjjJJJ())Q(v.[___YYYYYYRRB4ydt:@AUWW߹sѣG,qȑb^~]ZZ3k֬)SPV v`\nhh_*##3uToooooo---455!!!ZZZ-ZrѣF87&y9::.]t… C5߼ywvv޵k( @L@~`Ν矛-[7X#G^zeooȑ &X׉z𡅅ݻW\ӧH$Ȅee'Ι3ت%qлy͜9&;;СCD/aaaO<ɱ:u#(D^<~xʕf|why󦢢͛7DWpAq+V|wիWƎommDt9Nx˗/ w޴iӈ.g\-[n߾=k,@(oׯ{Ç%JO(ʙ3g7oޣGLBtEAW.\ظq3|>ٲeO{] nڜ|7oddd.>BTUU1BKKѣG 999g&H {]+(( nϞ=fff 8|>D"X,րG@C]_|9 w#t:/ q ׯx<\Fpp8 TUUh4DZHpp0 >>>zzze|ijjXGAE^^^q:d2;?L/ qB@A!C>r{{{SSS+**uKWIVuuuXADCC9`CCC;a2t:VgXMMM/]4z*++<bB 3AG_Fm۶܅  ą޽{¸8m۶1F)++O2%<<_ dggO0AAAaСgϞ@m `C-[eggxZ$%%?>&&ٙZ8GVVyXXDۊ+޾}; 7xxx.NZf ѵqnܸ1|kߌ3SSSLBڵkCBBRRR: wޝ;wnxxѵ'q/zzz/^d@t3g] 8]{qѢE.\+U'NR]-#KK7nhӧOKtWTTxxx@Hp 3f̸u֥K֬Yywp^|1 RBtoAAAw$~{ⅽBDD. ,GӧO+++;vǏ.X,޽{M6yh===+@ ssD777իW]Q/lmmO8qԩ7ot+@ A/TUUoܸq޽G|r;;; 6H)YBٳsss׬Y{ѣG0LGQQ7|cjjuHCCC@D:q?>}駟]y+Vx0..ܹsoo6l ++KT1B DYY٩S.^tR___!͛W^ɱھ} 2p08Akooѕ+W('OljjLfbbbhhhHH,Xt.$8AUUպu޽{WUUР`ee5j( 1[[[߾}d2Gֶo߾իWî78ŃfϞ}UV%%%#"++kll5deee2 ##bWVVhѣG;999:::99%&&Gx@ āಲ̙ty1'''+++??F.f( CSSSKKKGGGOOt#G]WWgggK8 8Hmm˗/eddDlYf矢Y#b r-ZbD]rիΝJO`W^ݻwOSSSī1cƁm&wp W._͛Dݐ/Xŋ #G$!"..}?3eM0*((X DVVVfkkkaa: '[[[{W`cgΜ`0n߾Mx#2|𠠠Pb@q?7n{葲2ѵرc $D n ѣףG_6nݻ+VYXX]A_=СC۷o'nl//uuu@D A|vڴi׮]#UTTښ>yJw`Pkjjrtt',֭;vѵ z-ZTSS( Ș1c^onnj*@Ћ{>}Ν;D'so7mOt-NX.]zjk7cƌo&&&JB(55e'N ~8CӉ.aݫ9rӧO%J\{{{__߫W] A7lϿu떄&8 &&&o߾yljaؼy۷o>|(韚x}/_.ѵ`̙͛_zEt- N_tׯ/\Zϟ8b`  &l޼СCDBf''' @t9S^^nkk;zǏ񣭭ݻw@1b͝;W^^ې([n=x_% B\eddtydƍAAA***$<===gϞΏ|qaaa|r&>rĉ?͛&nݺjժ v;v-!601q|ׯH1c<|033sԩ?Ӯ].MLlOOϊ7o$$$q8{ҩMCCAp|||nܸ0ڪdgg|GR̙s-K{p8E:ݿ?nhhhnnvssǦ6vL.p8 D@ ĥӕ+W۸h$[nڵp'O<qNB\.˧([PPa}||lv(Kdd$u Bn8lDQ#Ϫ/A.URR"? '&&&111J̝9sʕ+JJJ4gT͛7E_)&//Ĥ˃4L&޽テ `(//߱c͛7)J&&&999D@O`O\\r$zzsҤIǦ}.榧U=*|>ʕ+v)^hhǏ Flm533~HѮ_Nla| vAh A%%%.'Nmܸ0nذٳgd2ihhaLv E>?? ??ӧO}D"kii;vȐ![ҥK$% T`qM6UUUϟO4Iȸ䆆ohhhhhh``ihh())UUUaXG盛*+++++ G`ܸqO>RO^t)ߵijjڹsg@@'Nhgg2t!.\.7&&&$$˗$,--t:^몬LNNNNNNJJb0&L5kȑ#Z e;"bjj$혐g؎/ _zuݠQFM4Mdw|˗555666~~~̓{HlG Iuuu/^D=z333-DGGoٲE[[L&O4Ν;,تlG q rJYYYM6芺p8<6mLZZZ.Jv B| -[FR7Ç۷oWPP<|pkk+؎@ADKK˞={deeMLL^f*++w`0 .H﷐aÆ)++8qB~;+--]x1D2eJQQ#޾ehѢrATTԨQݻGt-H***3fҍ7O---k֬Ad˖-r#2}oaaQPP@t-Bqm:>k,>KHOL2f̘`}Cbcc+ssO]`;!ދ|''';;{]peff:99a'`;i!NUU5""bLbŊ3g] n`;iFxbB.`8rz]8%%[YY`xzz@ABBBDHv EUKv݂Qpp0ֽy敖VUUYJ %K 2혟keeKk;@wᘘ,Y2zr:::W#p8SSӡCx{bBR#_`/f qَ {+((ػw/х"***##ϯcbk `␐e˖ӧO744T)1$e;"rݻwSTǔ0@wܹsf211!^|Aw~2<<\7mnܸ! L:#!ލׯ_ 2ܷoÇ\. ?DB.Suu/QVVwww C$77WUTT&MA!mGᑂzAq^][SS# WVVV~Ykkkyy;X@AAٹKlll kkkeeewwD+++Ht///A;*//Aq R?Ͽt钂pHv c(Iߎw#77wذajjj nnnEӵ927 tvvf0Ǐ~:ںu+K|> $iuKKKiiEҷ#^$};lݨpS~zڵ77#''goo񥥥nZZZYY ;w@ 8Q$h; oG B CAΞ={BOOO%%S,א!CSkjj"RYY  ?@II STTDIq"AQ$};lШpdɒ/^߿Ϟ=رc d%_a=$%%u^So$mGAwCKKK***#4$)44ctU^^^TT8iii_{ZGGAWWѣG p[n :GomG$"AQ$};l?~|eeeNN_>==dVVV>|{xxt<;y3g477lݺ}%M6y󦥥۷/9y$,L|rmm+kjj6nܘ '''`QQQUI$AQ$};^zmp8Ǐdu֍5 ! u,P__zj:⒘hccn]vu8)!!ݝ`tWW/JNN6mpk֬e(BHv|їT[|yrrrZZIwY[[x{{]v0@w/33ӧO'b,\0--ݻwds(`ь3>|(7RBBϟOt-(BG?~ڴiELϥ(lG؎']RQQQSSۻw!_배0鸛0lG؎GĞWs<oɒ%D""?#BA?"5`;)! 奭Ft-Bw1ty lG {Nt-vI"=Jt-H%>ioo?ӧ;C:Ξ=[FFD"\+.?P(__ߪ*MTT԰atuuA xDGG6LSS?]ڍ7J_lG 5 qttLHH `ٿvB|RRRz{{]N_X˗/R-[]`;I!.Ǐ;::"bG200hV*(( "1H.q<{ۛL&ݻ7## [`֭[?~HtQbJ"#Fۼy3lGǏ٣ ѣۗBaʖ+Vzwtt?ZZZ)Fv\l"C IDATN?~|ff&!%q`޹s\CCsĉB-%%˗qqq,@xJ1((b-[ݻܹ ą"''sĈ3fniiQRR;vq㬬cS~~~NNNJJJrrrVVtw㥦*>ۑorʠ t!I&=}}bӓ2223#Fhjjjiikhh3 A(J[[[{{;ϯoii.---**p8hhh3fܸq666666#G$ۗbÇM0X\\g. h;/]ÇGYv} q:yd33PEEn?.((SyyyUUUuuuuuu[[[SS \.WNNNdeeeuuummmuuu---###CCC###hT233njsӧ ʂ8pɓ'k0"xIJJ2dkSSCݾ}7npߓH0lj522иwCDp[///[['OoT###Ϟ=KP䔜hD(33ɓkL nݺUTT_~@kkh\b B\ f͚5kֵkרT*ڼy5kvڵ<81zhwwݻwX,bB|g͚dH].Xɓ|\~sΞ=kcc@|@۷,Xjժ/瞥3غuk^^D"]6==]MMT!Gt *c+;vPUU*ܦO.\'deesss@؅췀%Kl߾ȑ#DDA3׵"[n}m{{;YS>|T[jWDœ?L&ڵK+=q3q8#F~ 2yϟ? u]@4 &ѣ M&EsC)**644{Eqqq&&&***W^ALաCH$E:qqبzjѬVAAٳ"XWkk-[H$uubO?u!.Dp>իWFBaaaZZZ> &zߓ˗/rȠhϟJ߽{˼}W__Edg1m۶B_"^58Ds>KBְ'wQWW700񪁀[>}Zlu떐>+^k[YYoݺdr@ DS\.wʕ222DͲ{.DsaO\hnnDH`Oh߸qݻg& j|$dk/K.MOOа߽{7} +"v8ŋ={F`'NBgv!k .^(//oooMT/`O_X,ܹs>|Et9Bo_4aߡ3g%&&r8c<J@+"FL3 FDDѵ81DLl赆ľl6СC4mҤIEEE{hmm}܈.iǎ+W|baa=DRwS\\liiy%b_Giiiꫯ={&Ğ͛CBBD|aRRR֯_a??j+Binn>}w"##mmm.C4 PN?tPXXXBBcx}}WVVVxxˆ.ְ[&Mz̙3g͚5wܺ:+;LRRRmaaAt90~>0, %%/Č;6""╕nnnD#V3 ZÞx{{;sݺuDW4x lll044$@$q;z/^]HWo߾{߾}KtE` &옘#F] BL5쉿jjݻY, :.ċݩTjDD/ϵ6l؋/Ξ={[[t+\WMQQ1**JGGr|fbuaЏ駥)**EixNNڋ/Չ.Lgv^kxeְ[طoĉ hP Yo!ʚ4i^XX*tǏLLL:]v͛Lܹ999b{4:}}#G|dgg={e޼yD֍K~ȑ#߀=y(hhhL0Zcl%؏?8lذ_|CÇ$ AQ[[ۮ](ʴiJJJ.GI'%% 2Uo믿RԞB}:Bٵk$'!lJ7o%zj4$6dD%%%_~#zǻx"HIIl{{ҥK߾}KHmGCc7skiiNlˌ3H}9D"HxDW]ypĉ4m ܹAaÆ544X9utt>|@t4g/D{.Hw> "''Dt]`0:Q{)E 8qBVV177GGG4mܹD($;}ajkkq ПcyyyIy/V\CB455%}zDDFCE&y<Œ{z{{+(( 2g:Nt9-66!Riii666D5pnnnhST :11qƍGii)E7n"<1ㇼqr+*****\nSSÑ***:::jjj}T*KS(.;lذm۶[NNNCX˗/^X~~Z[[!H;7H… ֭p8!zxx>|)8άYRRRܛ`,mmm߿(((999}}}SSS ssqƍ=ۣYYY杋hl6~gIW&L@":ߊxhkkۺuk@@כUUU}99J]f͹s>ڠ3y<ޛ7o?~d2vjiidEEE*d2kkkKKKʊ333322X,:W_}5lذu͛7/88 hgԩ{qvv(#%%UV}wyy#FL8Q ~~Ν{nnݺ=zLL̆ tuu144\jիW?}$Șl6;11~ꫯI~嗒̎T*uٲe|#B"U?G|I~~xnO߲@!tYKKKA~Sc2O t:}X#.ϬY={& mݽ{wժUhS/纡h?=xm!t mmm999ϟ[VOL;wFI"ܹ# ?&M"HVVVП.?ġ?/^={6@#)ʞ={?_wss 2DIIi޽DMݐW_H$;;Bj͎mlld2n !dkkKv!3$''׋#llПݻwOFFL&C:kx_FM8B_x<޵k״K`2?e@ˀO!^[[;ydvaq8**~X@Ġ?ؠ?ؠ?ؠ?z"ssC~(ʆ D9S>Wx^^ϟESth /--9r8黨(yy+W z kkkt6єPty w=UUU?~(:pw2!?ؠ?ؠ?ؠ?}}?|D"{1666OtL&pѢE^7*%%`xzz5rmmΝ;&Pccc llBϗ Ǐ"ay敖VUUYJ5S}S&sjKȇMRGo7u277鸍$155:th{{;.`2Æ !(,X/l===aBAޟnu 0AVTDD 7opX,.A?|>ϛ~ П@۟nu֭[fffD|Aw~2<<X DA?.ll[]C<:: љL}|}}>|qlA;ǎCuwwG$))K/===A555ǁ`qD _!kt u}>:zv.{֭CU UUUG`qD _!nll,'' ֯_d2+++{yxxn2˵+V(//ٸqc^^^@@5$''[ZZ >#2llxG]>a>JnݺQFi:88t+=99yڴiJJJ  P<OMMɓ&Ś?-d2.\(### (666O_|>~7$i׮]Bݧ>~䤤sQ ӫCue|40wUUU577=!@AAA05|~ff5N}zN>Ak׶X 'q>b>`0]F<5vG lllПn/Q˖-R?XUTTsNyqq;Rb ![|ڶmDp0b=zחBhkk=zE+0666O8#F@lϞ=ill|eH$;wL&666'Nj 铲˸q,,,tuu5{}|||tttJJ stt={ٳ /XĠ?ؠ?ؠ?y ޿իRALLL,+++///++p+**rssY,J5jĉ'NoD```?;y]FFF~~~yyyIIIEEEcc#liiaX F)***))ikk뛙=ZVVVx666477lpCН;w͛'G&! B$8H0q` @A ! B$8H0q` @A ! B$8H0q` @A ! B$8H0q` @A]Ǐ755XZZv5>#H666T%8 C'BYt)!%B0X,YD"uyR. ŗ)6!B0X{zzR(.Yzp!D/^&L9s&B0̜9FR***Ė$ q 8}t4ǹ\ŋHPeѢEA:Ct9 . ̙3N]`tx uuu ,cvV[[ۈaÆ=y=B&䔕 cIؘʎdN+4UUU :ΎB >}DjjjF ENN(JCCCf7775tRWWWQQQ^^^RRRQQVATÍ,-- 8@\} UQIDAT):::)))---55D"1 P##ÇȈZAFimm=f{{{'''숈بb++cZ[[[[[[YY)))]cW蟙B 2f & oƾx hN4Y. HHHIHHxcƌ>}q㾜K@b޸qӧcƌ6m}M$WCCϟ}tLLΝ;.\8H!jhh8vnݺfuuu/)))'Ny󦉉YfkB <… =߹s-[K@rssΝqƝ?~}|!L_ffĉm۶z?LLLdEEE۷8gǎ7nJLL_% dmm~?۷Bŋ?C\\5IjժL &\~{yS>yˋr$vhhݻ.]/$p8uԚ7oG%H$I__/?~ĄJڝMLL\|#tŜ9sΟ?_PPSݣP(G믿9r`&NOt-|>ommL~~/:Bv.c*sάgϞM4 O6-|2D:qDBGIID򏾄 ~6Sw\tgڴi" q>a*Sp8@ vvvgΜYv-ѵc̘1՟?X Q__urgcǎ8'''6}(/|>ʔ)]&Ocl۶e͚5D?p{%|>_4 DpB~~)qFEE?kl&o>333yyy555__߇r\A~' ӧOG(vv{lll*Bʪ/ s8۷o{yyikktKK˓'O7~ȑ#ϟ…O@*ڵkĈ\.W^ٳ;v ѱs 2#/cbbddd"##\cOuttyM_~ :uLޱcG_^w_F$%%sppشi1ɩ#&&&qA:IOOG5bxBBB_~葛[G/^Lr N ܇LMMqpԩ_^vm||111),,8`ٳg^ZXX驤4u3dȐ.555jhhطo*z~Νqokk8`TTTjjjpD"-Yŋg>vXd2bu~I}}444tyo4bݺuT*500g[2~w͚5<?~A>pԨv~B0p8&#F>D X@GG򢢢/innNKKݻw011ٿ۷o/''ŋs533CjkkoٲECC}emmm z `~t&YYYya>ɓKKKϜ9\PPunw6m͛o.^XFFɓd޽w^~ݻsssY,VII˗ݭ._.FPˏ9R]]͵wǛ2eʿ)`PihhPPP8w^[nԨQu T}}իutttKbb fv:r==wwwA]]]cbb:(/KJHHXdСCi4zdž'O2TUU[]FKKkwFǴ9~~~]q߫]4 3gNddĉ;?!HCC+WEjXXX^tSpL ee_vٳD"Lyh4Z(Xs@lݺU__ܸqD#U\E>}j``p{6nZQQ~z&_]hjjBرc]B͛79sFVV$[^^̙3kkk{Z p߹sg„ ޽#I/;;;Dggg!xꫯ޼yCPllld2HN322Zp… q~!ikk 5444i-谰gϞ566̝;ǵ@rCCC?~rԨQ&Lpqq744$%Rׯ_GGGVVVO8{ȑX)8@ļ}b)**ZYY3f̘1C|~IIIvvvp8uuugg &8;;r/ ⫽===#%[ZZQWW722266666>|nVTTz#M]]1 qx34///??]@NNNWWwȐ!ʪWa(AVgo%:l@[s o8.yTUiz'W$eYۼ`8º✛yY=k4Ml;4k1۶8o'wM|Qt5˲E@Ĉ8D@>wIENDB`anytree-2.12.1/docs/static/weight.png000066400000000000000000000306531452550712300174730ustar00rootroot00000000000000PNG  IHDR%H_bKGD IDATxy\?3YH&lJq J+*hUDA_Vk{VkkEknT+EDDP!RY%͏"Ff2I8dL<)))DEӭX,G}doo rjzB5˗@  :tڴiӧO6l  YYYщGf^^^^^^] Ϟ=HOOOOOommuuuOVXaaaaP$nڎ;pvv޳gϋ/秤P(7o1$Ç[n)g\> s\\X,VЃ LD"Ǎw@H?GdҤI>$ǏO8Jnݺ6M"֭[Dt9&b~RDϛ ><++r4Lg͚ER8+͛GP8@t-&g޾}`mm/'bCdB!=xO>0;;ҒrdAd˖-F x"*ƍD_sYtoQWZAZZZjiϟ?gkQgD.u]N8A"RSS.D]5E$%%-X >>O>!1SEM O2… D2 MMM,kժU _㕕?#х ]yZL|bqTT뭭ᦦǎ##۷o}vڵD --suww]ɑOLLх`&$$655B L|233}||pڹP(133c0NNNG8 >|1cݻ(&G%%%nnn8K.1cFAAAyyyXXؖ-[oߎpRxa`rDbf׆&&&7n >rHkk+~#lmmpBȡ`llϟƸq㺻9N#pBe :t(!!677Kwvv4"JWW!4<ԄݻvBrQQQhhh &GyZ]];DYYYfff6m:t(z?z[]]~ &G666t:ӧxL&zz <ɓ'pPȁFܿƆ?9rs炃>>>&LiPDߥvMϮ]Μ9SVVF&l6;//ёZ <'$$֭[DSNM8F^095j'zKpK.YB<[[FF׍7f͚Et-h4kQ309Gt:ѵ(.''Cz L"Ǎ|G]?QF_(?sll,ѵ(B,\ٳ06)hҥ6lv͛7L&ѵ-"hٲezzzw%_|LNHH 3 `ҥ4ŋDa|>?88XKK+::ZL@D-[ }v@@t9UVV5L6N:3eʔbkC||J5l^:77999ݻ]:88] :E 8p@WWw̘1ϟ'v۷3 {{{8:`rW^^rJ b~w޾}m6]]]SSӨ(U~`rRTTj*---##%s%fff{A &__}ݎ;rss=kmmMLL\r%:Mill,p]|޼yS(/\? 2㣏>rttkB(???'''33ӧb͍d655-^ox'$IPPzoݻwi4Z~~~zzzFFƽ{*++FFFfff֦C h4M(Լ}P P(czyyyzzzzz2̗/_ rܹ˗M;v۷0|z@CCC^^)..imm:::ZZZzzzfffVVVvvv~&%%O$wpD٢抍EoC{OG:/ؘL&:/N(.=zj*DB"6m555s{NA &{o޼?>:`dd 矧Op8AAA"H 09koolٲE5P1c]cנ`r$/_ iӔ3Cg駓'OU#G?,.66}6"""--b4LfΞ={A+WJtE`֬YhI݋-*..&"{d:Sfn޼y+09(++>|x޼yDWGE)((Xd z+4@09.a+Bnܸ_]&H:~80%%EE?3 [lz*b%$${1MQFI/mڴΝ;DW`rwitv\##+WQeQ{EEEDW`r^RqqqqF@cc~k<E~O>Ǖ+u5|W6ȭ׷g[[n]*&G>BpŅٳgGFF]"w9:uرcDW~`riӦ۷oƎFyeS7oNNN&"5#G/cc+Ww"/])CtE&n޼_TKOUVTkkN?`r^:v&"8pPZZ*`}L·o}477˵k]zΔ /Ld/: սr EtEj&6lؐ !DWÇK/mݺ5))TL, `2)))DW#wws!"-[GtE* &RRR+NOHH6lXSSӉ'f̘add`0lll-[s\ʊh.\33ŋo߿V<{l޼yzzzYYYX*QEq8!C9<144B>|###uџ/<$((_`>"J,/Y}ԩ]]]r==''`,Ynڵ ƍ8UK >Ս=ٹst{hhhXXXG>{ `ccy P(Fv>ollLΉ'XOD杝Pb(09|///?D`0H$X,ƶ >?~<{Dkؿ?^Jٸqcύw\t 2_f͚t˹sH$Y-xJ\TT₞L"11Q[[ D޽ 0aBύ藚Tطo扉:::PYYYfffΝ{8WW׳gϢڂ=z++czwF!B._a ƃ>j&fSX ȩS&M$ saʙGfΝ|>E$} tvvʻDP٬]PUUemm6]]]]+'>nݺнxɓ'ʸsNύO>̜9J s@WWW@@@yy9 00pΝ/ ---Ϟ=K8LW'Osѣ- b-[N=z?WPD"C±cZ[[KhAx`0rss?}K {)--*--EgvuuOLS5sswMӗ.]ZUUU__NP_R6xc>d,RSr~ٳg1BKKKWW$$6D,hm...>ɓ'3f̸wTA vDXoޝpBЊ+oNtEDKKҥKF\|-mtɑH$!!!wwwk\LLLh+"KΝ;ccc#FHHH@B򲷷x"LH$k֬&"e\ɉMkjjJtEjm&V۷oHQr߿~N 'ɉ޶m555smmm%",yt܃7@?p8 HDtEJ2(_SS ωHsPxtk׮)J@΁ iⲘ366NJJB駟NH]CRo߾Mt9r[~޽Ktr۸q#Z<9Pdffر__~ؘ7~Boǧ׷[@qr<==>sKSX,x76T*u˖-DWVGGGAgD_|8& B 6LW6Km۶JLͣaaa L&#֓Mkrv- P(t:tرMMMD6 O___ޱ\\\;TjHHQ`e&M*))7G;J={к*}"vZ@@tgϞ^'l:;;K'Xz@erV\D5kHqq|A'''+~^dkTIOz"}ϵ;I$ҨQn޼It RVVFt9}a>< ~qww*~黢ꮢAA܈3UUUk֬A  ?],9_yM]iٲeD1}'N]>|o ƍ#.{tmmm~~>)))mnn :::ZZZC sppg0v7߿}C"Ǝ믿N:oW2*i4Zb 6m)}sgg^C'G"秥edddff5X,sssKKK&ihhH&y<^SSSEEEUUUyyyaaaWW ///OOO6d2buee%Je0 #dA?ڭ !O]]_}i " ?C7ɑK(nڴ ]M/22 G W^]ti۶m'OP($ NAPzM8pCG6lP 49{6la׮]=vM+W\]]/^.cccAlz322٩QOΫWVX^ߺukAAK PpB d2^uԽ?$͛7˗/'vvvgϞRxxp۷:422RȦ/9`:::666Įt;v;wD G6Mp8 c޽;>5ktttX l֟&ɓnnny?422#4?NH$ڼy3DUozxxݸqCȦ@r|ŋi4Zll,K |T*… G6 D }}T̪ÙX,/dX?ivd%gӦM4MڰaNOKKu4?M H||<N$-Zdw5lߟ[CC͛7VlmmgϞ-19l?}'g<p2ad #9},ӧs2d̙3ݻCCCGPH$666d2=uӧ1c?{ !dxzzΙ3ea0K,[v-B .`0U)..svv#9jݟP r᪪ {{{22;9^BX-H$rpp077DBemm+V0aV{Sf$IPPЏ?miiGr$ܟа[={pwrvammƍ{nܽ{7ҥKXeG"Hϻ D{;w+#wL0F;w`5ʴiLLLڡ2PLjݟ^:::x<%GiԽ?g϶%V<!C |C%$$p\Nvvv.Y҅hkk~Q@KK˳gte㘃.IW__x~~~{]vmaa!z0** XxhǏ|̙31U__/]mo Q8tcƌTGr  &׵D"QVV٦M^%x=eooҥ.bbb͛7HQݻ9Nbb"Fgzsl`TIDATll>LfՑ</55ĉD:uTcccHHHuuuCCCDDDQQɓ'tkdi%GiԴ?gΜٳgσ51wz]1Xz5V+OՅ[[[ST&jժ 祳'OΘ1;>%Iii) X Wf{9y$&Է?2NL1AN͛7:odbx(l?#g)0||kk/6xǧ 9B߼ya;|0N-m|CB `d$W\A$..p[YY555agC; _~[a:vLo/4?MNgg-V7)SRRBٳg~CȦ5SaUUѣ'N?iiik֬c>`d|`^bkkkGGGl/ҥKt:=00P9M z}FՈHe~G6MOijjR'vE+DWW24?]yJ,GFFRiӦzQbsL#0[I2`dao'OL4Jnٲ^"#OOO2~zUx #&GE"ѯ:tP==_D}GѣG'4?.H>lnnNoݺ~ ȸq𾴪4? &~ĉϟdՕzj###*hѢ۷o1`dS (9R9997nD5jիϜ93UANNNdd>:a\dddUU&5+lD|Ç>200ppppttdXfffVVVL&sȐ!ZZZ:::ZZZVWWs8իn&9mڴӧ TȦ^29=ܼ<SRRRSS# e?K[[ʊb9:::993KiT?x%]bU www9tuu---1KMϺu$kXXP% :OŋcccCU@ RL)&AɁ E@"`r H09RL)&AɁ E@"`r H09RL)&AɁ E@"`rg͛g``흕EtEL{^AAAiiQl͛7ks|(t/_6660 H$rpp,**hD9xcJp8-Bc AAAW^%A&G&Ls#;w 䨸6@_,--`rT@ ܨ hjj"&Ћ:%(09*MKK s#15Ar=7VTTlmm 8SSSǏ{nD9s&15ANʒpKJJt:@$999[ BcJCԩS!!! EEE'O!L2e[ZZX,ֈ#fϞMt]sqqvU@9RL)&AɁ E@"`r H09RL)&AɁ EO^sAutt~: ৩UqccwǏWf= lML4D' ȤI\$L Y|B(H6p>_P(2D%ABtttTj"H"LjYlYwww חzQ-s﹅J.^X"`rTV````KQ9w9gamm-߳V-rlСCkii}06*&GH˗'l ((芠>5U1`iiY^^gSA𘣊&N8rHU`lT<&^uuuIIIeeeeeemmmssP(DWg0܅&###333/////@"L&444$T*\ x#Fprrrqqaٓ'Ok(L+99B8:::88X,&kh/_p8999o޼hnnn . 6lr&wEEEQUUekk`ӧ{xxЛ7ooݺuՖ &csH oooA{<ׯ__r%[~ׯEJJʔ)SIIIBPiC766FEE=J&c%%%sx{{?x2D"Q\\-JݴiS{{;Qh*~N;99eff]D"tww=ztjj*hlp\6M8 .\… d7|FXO]{?~NϜ9Z4*=PIIIK.%Y={6o޼C]z Hbb|˗/xlǏ@ !9Ș3gΪUkZ}^܆&GAƍ9sfLL L&N:uĉ[109555D,6}ለkQKu"9}E5 `ԩvںutwH>^SGFFFvod2D(:88,YQ4Lܶmfjjڊ΋ND"A_&`''_U cifd2i?,--p̑H$ׇ s䓔eii? nPPP[[[zzUw09ʚ6mb >>>fff ȑ#bX;Ԕbݿ_= RD k{zRR`߾}uuuG%H[n;[k'rYf dL /^PIIIl6t.ϖw,#9I?رcܸq ?}pgkr@w266VOMMeܸqG;ظ^NpB9xI_СC \.YS;E"9r@6G~~~{]vmaaX,H$QQQ@(b^LLL~#& U"(++lӦMCEx<]L&s;l`r```0z(\2f###y<^jj'weggO8q;tɓ'ډ\jjjINNVssҥ%KY[[] 6v]UU <[ѹs.3O^t)`r䣥zȑVk@BBYnхx&1cƄD2 Byѵx̑;:3k'9>>>UUU=RWӧO?rC109 *//?~<͎SY^~=uɓ''$$ndmmvkjjfϞmiiyy(#&&ԩSk֬ D/eee^^^\v 809_]tuu} ױcǦN䔖27h3fhS , ɻw+Oa&3"h߾} !--r$D >> import anytree >>> anytree.config.ASSERTIONS = True anytree-2.12.1/docs/tricks/multidim.rst000066400000000000000000000041431452550712300200570ustar00rootroot00000000000000Multidimensional Trees ====================== **Application**: Tree nodes should be hooked-up in multiple trees. An anytree node is only able to be part of **one** tree, not multiple. The following example shows how to handle this. **Example**: 4 objects `A`, `B`, `C` and `D` shall be part of the trees `X` and `Y`. The objects `A`, `B`, `C` and `D` are instances of a class `Item`. It is *not* a tree node. It just contains references `x` and `y` to the node representations in the corresponding trees. >>> class Item: ... def __init__(self, name): ... self.name = name ... self.x = None ... self.y = None ... def __repr__(self): ... return "Item(%r)" % self.name >>> a = Item('A') >>> b = Item('B') >>> c = Item('C') >>> d = Item('D') The tree nodes just contain the reference to `item` and take care of the proper reference, by using the attach/detach protocol. >>> from anytree import NodeMixin, RenderTree >>> class NodeX(NodeMixin): ... def __init__(self, item, parent=None): ... self.item = item ... self.parent = parent ... def _pre_detach(self, parent): ... self.item.x = None ... def _pre_attach(self, parent): ... self.item.x = self >>> class NodeY(NodeMixin): ... def __init__(self, item, parent=None): ... self.item = item ... self.parent = parent ... def _pre_detach(self, parent): ... self.item.y = None ... def _pre_attach(self, parent): ... self.item.y = self Tree generation is simple: >>> # X >>> xa = NodeX(a) >>> xb = NodeX(b, parent=xa) >>> xc = NodeX(c, parent=xa) >>> xd = NodeX(d, parent=xc) >>> # Y >>> yd = NodeY(d) >>> yc = NodeY(c, parent=yd) >>> yb = NodeY(b, parent=yd) >>> ya = NodeY(a, parent=yb) All tree functions as rendering and exporting can be used as usual: >>> for row in RenderTree(xa): ... print("%s%s" % (row.pre, row.node.item)) Item('A') ├── Item('B') └── Item('C') └── Item('D') >>> for row in RenderTree(yd): ... print("%s%s" % (row.pre, row.node.item)) Item('D') ├── Item('C') └── Item('B') └── Item('A') anytree-2.12.1/docs/tricks/readonly.rst000066400000000000000000000062731452550712300200560ustar00rootroot00000000000000Read-only Tree ============== **Application**: A read-only tree data structure, which denies modifications. The `Node._pre_attach` and `Node._pre_detach` hookups can be used for blocking tree modifications. If they raise an `Exception`, the tree is not modified. >>> from anytree import NodeMixin, RenderTree The exception: >>> class ReadOnlyError(RuntimeError): ... pass Permanent --------- The read-only attribute needs to be set after attaching to parent: >>> class ReadOnlyNode(NodeMixin): ... ... def __init__(self, foo, parent=None): ... super(ReadOnlyNode, self).__init__() ... self.foo = foo ... self.__readonly = False ... self.parent = parent ... self.__readonly = True ... ... def _pre_attach(self, parent): ... if self.__readonly: ... raise ReadOnlyError() ... ... def _pre_detach(self, parent): ... raise ReadOnlyError() An example tree: >>> a = ReadOnlyNode("a") >>> a0 = ReadOnlyNode("a0", parent=a) >>> a1 = ReadOnlyNode("a1", parent=a) >>> a1a = ReadOnlyNode("a1a", parent=a1) >>> a2 = ReadOnlyNode("a2", parent=a) >>> print(RenderTree(a).by_attr("foo")) a ├── a0 ├── a1 │ └── a1a └── a2 Modifications raise an `ReadOnlyError` >>> a0.parent = a2 Traceback (most recent call last): ... ReadOnlyError >>> a.children = [a1] Traceback (most recent call last): ... ReadOnlyError The tree structure is untouched: >>> print(RenderTree(a).by_attr("foo")) a ├── a0 ├── a1 │ └── a1a └── a2 .. note:: It is important to use the ``_pre_*`` and **not** the ``_post_*`` methods. An exception raised by `_pre_detach(parent)` and `_pre_attach(parent)` will **prevent** the tree structure to be updated. The node keeps the old state. An exception raised by `_post_detach(parent)` and `_post_attach(parent)` does **not rollback** the tree structure modification. Temporary --------- To select the read-only mode temporarily, the root node should provide an attribute for all child nodes, set *after* construction. >>> class ReadOnlyNode(NodeMixin): ... def __init__(self, foo, parent=None): ... super(ReadOnlyNode, self).__init__() ... self.readonly = False ... self.foo = foo ... self.parent = parent ... def _pre_attach(self, parent): ... if self.root.readonly: ... raise ReadOnlyError() ... def _pre_detach(self, parent): ... if self.root.readonly: ... raise ReadOnlyError() An example tree: >>> a = ReadOnlyNode("a") >>> a0 = ReadOnlyNode("a0", parent=a) >>> a1 = ReadOnlyNode("a1", parent=a) >>> a1a = ReadOnlyNode("a1a", parent=a1) >>> a2 = ReadOnlyNode("a2", parent=a) >>> print(RenderTree(a).by_attr("foo")) a ├── a0 ├── a1 │ └── a1a └── a2 Switch to read-only mode: >>> a.readonly = True >>> a0.parent = a2 Traceback (most recent call last): ... ReadOnlyError >>> a.children = [a1] Traceback (most recent call last): ... ReadOnlyError Disable read-only mode: >>> a.readonly = False Modifications are allowed now: >>> a0.parent = a2 >>> print(RenderTree(a).by_attr("foo")) a ├── a1 │ └── a1a └── a2 └── a0 anytree-2.12.1/docs/tricks/weightededges.rst000066400000000000000000000025231452550712300210430ustar00rootroot00000000000000Weighted Edges ============== **Application**: Add weight to edges and make use of them. As every node has just one parent, the easiest way to handle edge weights is to store them in the child node. If a child node is detached from its parent, the weight attribute can be cleared automatically by `Node._post_detach`. >>> from anytree import NodeMixin, RenderTree >>> class WNode(NodeMixin): ... ... def __init__(self, foo, parent=None, weight=None): ... super(WNode, self).__init__() ... self.foo = foo ... self.parent = parent ... self.weight = weight if parent is not None else None ... ... def _post_detach(self, parent): ... self.weight = None An example tree: >>> a = WNode("a") >>> a0 = WNode("a0", parent=a, weight=2) >>> a1 = WNode("a1", parent=a, weight=3) >>> a1a = WNode("a1a", parent=a1) >>> a2 = WNode("a2", parent=a) >>> for pre, _, node in RenderTree(a): ... print("%s%s (%s)" % (pre, node.foo, node.weight or 0)) a (0) ├── a0 (2) ├── a1 (3) │ └── a1a (0) └── a2 (0) >>> from anytree.exporter import DotExporter >>> DotExporter(a, ... nodenamefunc=lambda node: node.foo, ... edgeattrfunc=lambda parent, child: "style=bold,label=%d" % (child.weight or 0) ... ).to_picture("weight.png") # doctest: +SKIP .. image:: ../static/weight.png anytree-2.12.1/docs/tricks/yaml.rst000066400000000000000000000047221452550712300172000ustar00rootroot00000000000000YAML Import/Export ================== YAML_ (YAML Ain't Markup Language) is a human-readable data serialization language. PYYAML_ implements importer and exporter in python. *Please install it, before continuing* .. note:: anytree package does not depend on any external packages. It does **NOT** include PYYAML_. .. Warning:: It is not safe to call yaml.load with any data received from an untrusted source! yaml.load is as powerful as pickle.load and so may call any Python function. The yaml.safe_load function limits the load functionality to built-in types. Export ------ The :any:`DictExporter` converts any tree to a dictionary, which can be handled by `yaml.dump`. >>> import yaml >>> from anytree import AnyNode >>> from anytree.exporter import DictExporter Example tree: >>> root = AnyNode(a="root") >>> s0 = AnyNode(a="sub0", parent=root) >>> s0a = AnyNode(a="sub0A", b="foo", parent=s0) >>> s0b = AnyNode(a="sub0B", parent=s0) >>> s1 = AnyNode(a="sub1", parent=root) Export to dictionary and convert to YAML: >>> dct = DictExporter().export(root) >>> print(yaml.dump(dct, default_flow_style=False)) a: root children: - a: sub0 children: - a: sub0A b: foo - a: sub0B - a: sub1 :any:`DictExporter` controls the content. `yaml.dump` controls the YAML related stuff. To dump to a file, use an file object as second argument: >>> with open("/path/to/file", "w") as file: # doctest: +SKIP ... yaml.dump(data, file) Import ------ The `yaml.load` function reads YAML data --- a dictionary, which :any:`DictImporter` converts to a tree. >>> import yaml >>> from anytree.importer import DictImporter >>> from pprint import pprint # just for nice printing >>> from anytree import RenderTree # just for nice printing Example data: >>> data = """ ... a: root ... children: ... - a: sub0 ... children: ... - a: sub0A ... b: foo ... - a: sub0B ... - a: sub1 ... """ Import to dictionary and convert to tree: >>> dct = yaml.load(data, Loader=yaml.Loader) >>> pprint(dct) {'a': 'root', 'children': [{'a': 'sub0', 'children': [{'a': 'sub0A', 'b': 'foo'}, {'a': 'sub0B'}]}, {'a': 'sub1'}]} >>> root = DictImporter().import_(dct) >>> print(RenderTree(root)) AnyNode(a='root') ├── AnyNode(a='sub0') │ ├── AnyNode(a='sub0A', b='foo') │ └── AnyNode(a='sub0B') └── AnyNode(a='sub1') .. _YAML: https://en.wikipedia.org/wiki/YAML .. _PYYAML: http://pyyaml.org/wiki/PyYAMLDocumentation anytree-2.12.1/pyproject.toml000066400000000000000000000050661452550712300161730ustar00rootroot00000000000000[tool.poetry] name = "anytree" version = "2.12.1" description = "Powerful and Lightweight Python Tree Data Structure with various plugins" authors = [ "c0fec0de " ] readme = "README.rst" license = "Apache-2.0" keywords = [ "tree", "tree data", "treelib", "tree walk", "tree structure", ] classifiers = [ "Topic :: Software Development :: Libraries :: Python Modules", "Development Status :: 5 - Production/Stable", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", ] [project.urls] "Homepage" = "https://github.com/c0fec0de/anytree" "Documentation" = "https://anytree.readthedocs.io/en/latest/" "Bug Tracker" = "https://github.com/c0fec0de/anytree/issues" [tool.poetry.dependencies] python = ">= 3.7.2, < 4" six = '*' [tool.poetry.group.test.dependencies] black = '^22.3.0' coverage = '^6.4.4' isort = '^5.9' pylint = '^2.15' pytest = '^6.2' pyyaml = ">=6.0" [tool.poetry.group.doc.dependencies] sphinx = '*' [build-system] requires = ["poetry_core>=1.0"] build-backend = "poetry.core.masonry.api" [tool.black] line-length = 120 include = '\.pyi?$' exclude = ''' /( \.eggs | \.git | \.mypy_cache | \.venv | \.tox | build | dist | setup\.py )/ ''' [tool.isort] profile = "black" line_length = 120 [tool.coverage.report] exclude_lines = [ 'return NotImplemented', 'raise NotImplementedError()', 'pragma: no cover', ] [tool.pylint.'MESSAGES CONTROL'] max-line-length = 120 disable = [ 'consider-using-f-string', 'duplicate-code', 'missing-class-docstring', 'missing-module-docstring', 'redundant-u-string-prefix', 'too-few-public-methods', 'too-many-arguments', 'too-many-instance-attributes', 'super-with-arguments', # TBD ] [tool.tox] legacy_tox_ini = """ [tox] envlist = py isolated_build = True [tox:.package] basepython = python3 [testenv] allowlist_externals = * setenv = ANYTREE_ASSERTIONS=1 commands = poetry install --with=test --with=doc poetry run black . poetry run isort . poetry run coverage run --source=anytree --branch -m pytest --doctest-glob=docs/*.rst --doctest-modules --ignore-glob=tests/testdata* --ignore=docs/conf.py --log-level=DEBUG -vv --junitxml=report.xml poetry run coverage report poetry run coverage html poetry run coverage xml poetry run pylint anytree poetry run make html -C docs """anytree-2.12.1/tests/000077500000000000000000000000001452550712300144125ustar00rootroot00000000000000anytree-2.12.1/tests/__init__.py000066400000000000000000000000001452550712300165110ustar00rootroot00000000000000anytree-2.12.1/tests/helper.py000066400000000000000000000023611452550712300162450ustar00rootroot00000000000000"""Helper Methods for testing.""" from contextlib import contextmanager import six def eq_(one, other): assert one == other, "{one} != {other}".format(one=one, other=other) # hack own assert_raises, because py26 has a different impelmentation @contextmanager def assert_raises(exccls, msg): """Check exception of class `exccls` to be raised with message `msg`.""" try: yield assert False, "%r not raised" % exccls except Exception as exc: assert isinstance(exc, exccls), "%r is not a %r" % (exc, exccls) eq_(str(exc), msg) def with_setup(setup=None, teardown=None): def decorate(func, setup=setup, teardown=teardown): if setup: if hasattr(func, "setup"): _old_s = func.setup def _s(): setup() _old_s() func.setup = _s else: func.setup = setup if teardown: if hasattr(func, "teardown"): _old_t = func.teardown def _t(): _old_t() teardown() func.teardown = _t else: func.teardown = teardown return func return decorate anytree-2.12.1/tests/refdata/000077500000000000000000000000001452550712300160205ustar00rootroot00000000000000anytree-2.12.1/tests/refdata/test_dotexporter/000077500000000000000000000000001452550712300214365ustar00rootroot00000000000000anytree-2.12.1/tests/refdata/test_dotexporter/tree/000077500000000000000000000000001452550712300223755ustar00rootroot00000000000000anytree-2.12.1/tests/refdata/test_dotexporter/tree/tree.dot000066400000000000000000000005061452550712300240450ustar00rootroot00000000000000digraph tree { "root"; "sub0"; "sub0B"; "sub0A"; "sub1"; "sub1A"; "sub1\"B"; "su\\b1C"; "sub1Ca"; "root" -> "sub0"; "root" -> "sub1"; "sub0" -> "sub0B"; "sub0" -> "sub0A"; "sub1" -> "sub1A"; "sub1" -> "sub1\"B"; "sub1" -> "su\\b1C"; "su\\b1C" -> "sub1Ca"; } anytree-2.12.1/tests/refdata/test_dotexporter/tree_custom/000077500000000000000000000000001452550712300237675ustar00rootroot00000000000000anytree-2.12.1/tests/refdata/test_dotexporter/tree_custom/tree_custom.dot000066400000000000000000000012371452550712300270330ustar00rootroot00000000000000digraph tree { rankdir=LR; "root:0" [shape=box]; "sub0:1" [shape=box]; "sub0B:2" [shape=box]; "sub0A:2" [shape=box]; "sub1:1" [shape=box]; "sub1A:2" [shape=box]; "sub1\"B:2" [shape=box]; "su\\b1C:2" [shape=box]; "sub1Ca:3" [shape=box]; "root:0" -> "sub0:1" [label="root:sub0"]; "root:0" -> "sub1:1" [label="root:sub1"]; "sub0:1" -> "sub0B:2" [label="sub0:sub0B"]; "sub0:1" -> "sub0A:2" [label="sub0:sub0A"]; "sub1:1" -> "sub1A:2" [label="sub1:sub1A"]; "sub1:1" -> "sub1\"B:2" [label="sub1:sub1"B"]; "sub1:1" -> "su\\b1C:2" [label="sub1:su\b1C"]; "su\\b1C:2" -> "sub1Ca:3" [label="su\b1C:sub1Ca"]; } anytree-2.12.1/tests/refdata/test_dotexporter/tree_filter/000077500000000000000000000000001452550712300237425ustar00rootroot00000000000000anytree-2.12.1/tests/refdata/test_dotexporter/tree_filter/tree_filter.dot000066400000000000000000000003131452550712300267530ustar00rootroot00000000000000digraph tree { "sub0"; "sub0B"; "sub0A"; "sub1"; "sub1A"; "sub1\"B"; "sub1Ca"; "sub0" -> "sub0B"; "sub0" -> "sub0A"; "sub1" -> "sub1A"; "sub1" -> "sub1\"B"; } anytree-2.12.1/tests/refdata/test_dotexporter/tree_maxlevel/000077500000000000000000000000001452550712300242725ustar00rootroot00000000000000anytree-2.12.1/tests/refdata/test_dotexporter/tree_maxlevel/tree_maxlevel.dot000066400000000000000000000001411452550712300276320ustar00rootroot00000000000000digraph tree { "root"; "sub0"; "sub1"; "root" -> "sub0"; "root" -> "sub1"; } anytree-2.12.1/tests/refdata/test_dotexporter/tree_stop/000077500000000000000000000000001452550712300234425ustar00rootroot00000000000000anytree-2.12.1/tests/refdata/test_dotexporter/tree_stop/tree_stop.dot000066400000000000000000000002351452550712300261560ustar00rootroot00000000000000digraph tree { "root"; "sub0"; "sub0B"; "sub0A"; "root" -> "sub0"; "root" -> "sub1"; "sub0" -> "sub0B"; "sub0" -> "sub0A"; } anytree-2.12.1/tests/refdata/test_mermaidexporter/000077500000000000000000000000001452550712300222665ustar00rootroot00000000000000anytree-2.12.1/tests/refdata/test_mermaidexporter/tree/000077500000000000000000000000001452550712300232255ustar00rootroot00000000000000anytree-2.12.1/tests/refdata/test_mermaidexporter/tree/tree.md000066400000000000000000000003051452550712300245040ustar00rootroot00000000000000```mermaid graph TD N0["root"] N1["sub0"] N2["sub0B"] N3["sub0A"] N4["sub1"] N5["sub1A"] N6["sub1\"B"] N7["su\\b1C"] N8["sub1Ca"] N0-->N1 N0-->N4 N1-->N2 N1-->N3 N4-->N5 N4-->N6 N4-->N7 N7-->N8 ```anytree-2.12.1/tests/refdata/test_mermaidexporter/tree_custom/000077500000000000000000000000001452550712300246175ustar00rootroot00000000000000anytree-2.12.1/tests/refdata/test_mermaidexporter/tree_custom/tree_custom.md000066400000000000000000000004221452550712300274700ustar00rootroot00000000000000```mermaid graph TD %% just an example comment %% could be an option too N0("root") N1("sub0") N2("sub0B") N3("sub0A") N4("sub1") N5("sub1A") N6("sub1"B") N7("su\b1C") N8("sub1Ca") N0--2-->N1 N0---->N4 N1--109-->N2 N1---->N3 N4--7-->N5 N4--8-->N6 N4--22-->N7 N7--42-->N8 ```anytree-2.12.1/tests/refdata/test_mermaidexporter/tree_filter/000077500000000000000000000000001452550712300245725ustar00rootroot00000000000000anytree-2.12.1/tests/refdata/test_mermaidexporter/tree_filter/tree_filter.md000066400000000000000000000002141452550712300274150ustar00rootroot00000000000000```mermaid graph TD N0["sub0"] N1["sub0B"] N2["sub0A"] N3["sub1"] N4["sub1A"] N5["sub1\"B"] N6["sub1Ca"] N0-->N1 N0-->N2 N3-->N4 N3-->N5 ```anytree-2.12.1/tests/refdata/test_mermaidexporter/tree_maxlevel/000077500000000000000000000000001452550712300251225ustar00rootroot00000000000000anytree-2.12.1/tests/refdata/test_mermaidexporter/tree_maxlevel/tree_maxlevel.md000066400000000000000000000001101452550712300302700ustar00rootroot00000000000000```mermaid graph TD N0["root"] N1["sub0"] N2["sub1"] N0-->N1 N0-->N2 ```anytree-2.12.1/tests/refdata/test_mermaidexporter/tree_stop/000077500000000000000000000000001452550712300242725ustar00rootroot00000000000000anytree-2.12.1/tests/refdata/test_mermaidexporter/tree_stop/tree_stop.md000066400000000000000000000001351452550712300266170ustar00rootroot00000000000000```mermaid graph TD N0["root"] N1["sub0"] N2["sub0B"] N3["sub0A"] N0-->N1 N1-->N2 N1-->N3 ```anytree-2.12.1/tests/refdata/test_uniquedotexporter/000077500000000000000000000000001452550712300226655ustar00rootroot00000000000000anytree-2.12.1/tests/refdata/test_uniquedotexporter/tree/000077500000000000000000000000001452550712300236245ustar00rootroot00000000000000anytree-2.12.1/tests/refdata/test_uniquedotexporter/tree/tree.dot000066400000000000000000000006441452550712300252770ustar00rootroot00000000000000digraph tree { "0x0" [label="root"]; "0x1" [label="sub0"]; "0x2" [label="sub0B"]; "0x3" [label="sub0A"]; "0x4" [label="sub1"]; "0x5" [label="sub1A"]; "0x6" [label="sub1"B"]; "0x7" [label="su\b1C"]; "0x8" [label="sub1Ca"]; "0x0" -> "0x1"; "0x0" -> "0x4"; "0x1" -> "0x2"; "0x1" -> "0x3"; "0x4" -> "0x5"; "0x4" -> "0x6"; "0x4" -> "0x7"; "0x7" -> "0x8"; } anytree-2.12.1/tests/refdata/test_uniquedotexporter/tree_custom/000077500000000000000000000000001452550712300252165ustar00rootroot00000000000000anytree-2.12.1/tests/refdata/test_uniquedotexporter/tree_custom/tree_custom.dot000066400000000000000000000012371452550712300302620ustar00rootroot00000000000000digraph tree { rankdir=LR; "root:0" [shape=box]; "sub0:1" [shape=box]; "sub0B:2" [shape=box]; "sub0A:2" [shape=box]; "sub1:1" [shape=box]; "sub1A:2" [shape=box]; "sub1\"B:2" [shape=box]; "su\\b1C:2" [shape=box]; "sub1Ca:3" [shape=box]; "root:0" -> "sub0:1" [label="root:sub0"]; "root:0" -> "sub1:1" [label="root:sub1"]; "sub0:1" -> "sub0B:2" [label="sub0:sub0B"]; "sub0:1" -> "sub0A:2" [label="sub0:sub0A"]; "sub1:1" -> "sub1A:2" [label="sub1:sub1A"]; "sub1:1" -> "sub1\"B:2" [label="sub1:sub1"B"]; "sub1:1" -> "su\\b1C:2" [label="sub1:su\b1C"]; "su\\b1C:2" -> "sub1Ca:3" [label="su\b1C:sub1Ca"]; } anytree-2.12.1/tests/refdata/test_uniquedotexporter/tree_filter/000077500000000000000000000000001452550712300251715ustar00rootroot00000000000000anytree-2.12.1/tests/refdata/test_uniquedotexporter/tree_filter/tree_filter.dot000066400000000000000000000004361452550712300302100ustar00rootroot00000000000000digraph tree { "0x0" [label="sub0"]; "0x1" [label="sub0B"]; "0x2" [label="sub0A"]; "0x3" [label="sub1"]; "0x4" [label="sub1A"]; "0x5" [label="sub1"B"]; "0x6" [label="sub1Ca"]; "0x0" -> "0x1"; "0x0" -> "0x2"; "0x3" -> "0x4"; "0x3" -> "0x5"; } anytree-2.12.1/tests/refdata/test_uniquedotexporter/tree_maxlevel/000077500000000000000000000000001452550712300255215ustar00rootroot00000000000000anytree-2.12.1/tests/refdata/test_uniquedotexporter/tree_maxlevel/tree_maxlevel.dot000066400000000000000000000002071452550712300310640ustar00rootroot00000000000000digraph tree { "0x0" [label="root"]; "0x1" [label="sub0"]; "0x2" [label="sub1"]; "0x0" -> "0x1"; "0x0" -> "0x2"; } anytree-2.12.1/tests/refdata/test_uniquedotexporter/tree_stop/000077500000000000000000000000001452550712300246715ustar00rootroot00000000000000anytree-2.12.1/tests/refdata/test_uniquedotexporter/tree_stop/tree_stop.dot000066400000000000000000000003131452550712300274020ustar00rootroot00000000000000digraph tree { "0x0" [label="root"]; "0x1" [label="sub0"]; "0x2" [label="sub0B"]; "0x3" [label="sub0A"]; "0x0" -> "0x1"; "0x0" -> "0x4"; "0x1" -> "0x2"; "0x1" -> "0x3"; } anytree-2.12.1/tests/refdata/tree1.dot000066400000000000000000000004641452550712300175540ustar00rootroot00000000000000digraph tree { "root"; "sub0"; "sub0B"; "sub0A"; "sub1"; "sub1A"; "sub1B"; "sub1C"; "99"; "root" -> "sub0"; "root" -> "sub1"; "sub0" -> "sub0B"; "sub0" -> "sub0A"; "sub1" -> "sub1A"; "sub1" -> "sub1B"; "sub1" -> "sub1C"; "sub1C" -> "99"; } anytree-2.12.1/tests/refdata/tree2.dot000066400000000000000000000012221452550712300175460ustar00rootroot00000000000000digraph tree { rankdir=LR; "root:0" [shape=box]; "sub0:1" [shape=box]; "sub0B:2" [shape=box]; "sub0A:2" [shape=box]; "sub1:1" [shape=box]; "sub1A:2" [shape=box]; "sub1B:2" [shape=box]; "sub1C:2" [shape=box]; "sub1Ca:3" [shape=box]; "root:0" -> "sub0:1" [label="root:sub0"]; "root:0" -> "sub1:1" [label="root:sub1"]; "sub0:1" -> "sub0B:2" [label="sub0:sub0B"]; "sub0:1" -> "sub0A:2" [label="sub0:sub0A"]; "sub1:1" -> "sub1A:2" [label="sub1:sub1A"]; "sub1:1" -> "sub1B:2" [label="sub1:sub1B"]; "sub1:1" -> "sub1C:2" [label="sub1:sub1C"]; "sub1C:2" -> "sub1Ca:3" [label="sub1C:sub1Ca"]; } anytree-2.12.1/tests/test_cachedsearch.py000066400000000000000000000044361452550712300204270ustar00rootroot00000000000000from anytree import AsciiStyle, CountError, Node, PreOrderIter, RenderTree from anytree.cachedsearch import find, find_by_attr, findall, findall_by_attr from .helper import assert_raises, eq_ def test_findall(): f = Node("f") b = Node("b", parent=f) a = Node("a", parent=b) d = Node("d", parent=b) c = Node("c", parent=d) e = Node("e", parent=d) eq_(findall(f, filter_=lambda node: node.name in ("a", "b")), (b, a)) eq_(findall(f, filter_=lambda node: d in node.path), (d, c, e)) with assert_raises( CountError, ("Expecting at least 4 elements, but found 3. " "(Node('/f/b/d'), Node('/f/b/d/c'), Node('/f/b/d/e'))"), ): findall(f, filter_=lambda node: d in node.path, mincount=4) with assert_raises( CountError, ("Expecting 2 elements at maximum, but found 3. " "(Node('/f/b/d'), Node('/f/b/d/c'), Node('/f/b/d/e'))"), ): findall(f, filter_=lambda node: d in node.path, maxcount=2) def test_findall_by_attr(): f = Node("f") b = Node("b", parent=f) Node("a", parent=b) d = Node("d", parent=b) Node("c", parent=d) Node("e", parent=d) eq_(findall_by_attr(f, "d"), (d,)) with assert_raises(CountError, ("Expecting at least 1 elements, but found 0.")): findall_by_attr(f, "z", mincount=1) def test_find(): f = Node("f") b = Node("b", parent=f) Node("a", parent=b) d = Node("d", parent=b) Node("c", parent=d) Node("e", parent=d) g = Node("g", parent=f) i = Node("i", parent=g) Node("h", parent=i) eq_(find(f, lambda n: n.name == "d"), d) eq_(find(f, lambda n: n.name == "z"), None) with assert_raises( CountError, ( "Expecting 1 elements at maximum, but found 5. " "(Node('/f/b'), Node('/f/b/a'), Node('/f/b/d'), Node('/f/b/d/c'), Node('/f/b/d/e'))" ), ): find(f, lambda n: b in n.path) def test_find_by_attr(): f = Node("f") b = Node("b", parent=f) Node("a", parent=b) d = Node("d", parent=b) c = Node("c", parent=d, foo=4) Node("e", parent=d) g = Node("g", parent=f) i = Node("i", parent=g) Node("h", parent=i) eq_(find_by_attr(f, "d"), d) eq_(find_by_attr(f, name="foo", value=4), c) eq_(find_by_attr(f, name="foo", value=8), None) anytree-2.12.1/tests/test_dictexporter.py000066400000000000000000000075351452550712300205510ustar00rootroot00000000000000from anytree import AnyNode, Node, NodeMixin from anytree.exporter import DictExporter from .helper import eq_ def test_dict_exporter(): """Dict Exporter.""" root = AnyNode(id="root") s0 = AnyNode(id="sub0", parent=root) s0b = AnyNode(id="sub0B", parent=s0) s0a = AnyNode(id="sub0A", parent=s0) s1 = AnyNode(id="sub1", parent=root, foo="bar") s1a = AnyNode(id="sub1A", parent=s1) s1b = AnyNode(id="sub1B", parent=s1) s1c = AnyNode(id="sub1C", parent=s1) s1ca = AnyNode(id="sub1Ca", parent=s1c) exporter = DictExporter() eq_( exporter.export(root), { "id": "root", "children": [ {"id": "sub0", "children": [{"id": "sub0B"}, {"id": "sub0A"}]}, { "id": "sub1", "foo": "bar", "children": [{"id": "sub1A"}, {"id": "sub1B"}, {"id": "sub1C", "children": [{"id": "sub1Ca"}]}], }, ], }, ) def test_dict_exporter_node(): """Dict Exporter.""" root = Node("root") s0 = Node("sub0", parent=root) s0b = Node("sub0B", parent=s0) s0a = Node("sub0A", parent=s0) s1 = Node("sub1", parent=root, foo="bar") s1a = Node("sub1A", parent=s1) s1b = Node("sub1B", parent=s1) s1c = Node("sub1C", parent=s1) s1ca = Node("sub1Ca", parent=s1c) exporter = DictExporter() eq_( exporter.export(root), { "name": "root", "children": [ {"name": "sub0", "children": [{"name": "sub0B"}, {"name": "sub0A"}]}, { "name": "sub1", "foo": "bar", "children": [ {"name": "sub1A"}, {"name": "sub1B"}, {"name": "sub1C", "children": [{"name": "sub1Ca"}]}, ], }, ], }, ) def test_dict_exporter_filter(): """Dict Exporter.""" root = Node("root") s0 = Node("sub0", parent=root) s0b = Node("sub0B", parent=s0) s0a = Node("sub0A", parent=s0) s1 = Node("sub1", parent=root, foo="bar") s1a = Node("sub1A", parent=s1) s1b = Node("sub1B", parent=s1) s1c = Node("sub1C", parent=s1) s1ca = Node("sub1Ca", parent=s1c) exporter = DictExporter(attriter=lambda attrs: [(k, v) for k, v in attrs if k == "name"]) eq_( exporter.export(root), { "name": "root", "children": [ {"name": "sub0", "children": [{"name": "sub0B"}, {"name": "sub0A"}]}, { "name": "sub1", "children": [ {"name": "sub1A"}, {"name": "sub1B"}, {"name": "sub1C", "children": [{"name": "sub1Ca"}]}, ], }, ], }, ) def test_dict_exporter_mixin(): """Dict Exporter.""" class MyClass(NodeMixin): def __init__(self, foo, parent=None): super(MyClass, self).__init__() self.foo = foo self.parent = parent root = MyClass("root") s0 = MyClass("s0", parent=root) s0b = MyClass("s0b", parent=s0) s0a = MyClass("s0a", parent=s0) s1 = MyClass("s1", parent=root) s1a = MyClass("s1a", parent=s1) s1b = MyClass("s1b", parent=s1) s1c = MyClass("s1c", parent=s1) s1ca = MyClass("s1ca", parent=s1c) exporter = DictExporter() eq_( exporter.export(root), { "foo": "root", "children": [ {"foo": "s0", "children": [{"foo": "s0b"}, {"foo": "s0a"}]}, { "foo": "s1", "children": [{"foo": "s1a"}, {"foo": "s1b"}, {"foo": "s1c", "children": [{"foo": "s1ca"}]}], }, ], }, ) anytree-2.12.1/tests/test_dictimporter.py000066400000000000000000000045001452550712300205270ustar00rootroot00000000000000# -*- coding: utf-8 -*- from copy import deepcopy from anytree import Node, RenderTree from anytree.exporter import DictExporter from anytree.importer import DictImporter from .helper import eq_ def test_dict_importer(): """Dict Importer.""" importer = DictImporter() exporter = DictExporter() refdata = { "id": "root", "children": [ {"id": "sub0", "children": [{"id": "sub0B"}, {"id": "sub0A"}]}, { "id": "sub1", "children": [{"id": "sub1A"}, {"id": "sub1B"}, {"id": "sub1C", "children": [{"id": "sub1Ca"}]}], }, ], } data = deepcopy(refdata) root = importer.import_(data) eq_(data, refdata) eq_(exporter.export(root), data) r = RenderTree(root) assert str(r).splitlines() == [ "AnyNode(id='root')", "├── AnyNode(id='sub0')", "│ ├── AnyNode(id='sub0B')", "│ └── AnyNode(id='sub0A')", "└── AnyNode(id='sub1')", " ├── AnyNode(id='sub1A')", " ├── AnyNode(id='sub1B')", " └── AnyNode(id='sub1C')", " └── AnyNode(id='sub1Ca')", ] def test_dict_importer_node(): """Dict Importer.""" importer = DictImporter(Node) exporter = DictExporter() refdata = { "name": "root", "children": [ {"name": "sub0", "children": [{"name": "sub0B"}, {"name": "sub0A"}]}, { "name": "sub1", "children": [ {"name": "sub1A"}, {"name": "sub1B"}, {"name": "sub1C", "children": [{"name": "sub1Ca"}]}, ], }, ], } data = deepcopy(refdata) root = importer.import_(data) eq_(data, refdata) eq_(exporter.export(root), data) r = RenderTree(root) assert str(r).splitlines() == [ "Node('/root')", "├── Node('/root/sub0')", "│ ├── Node('/root/sub0/sub0B')", "│ └── Node('/root/sub0/sub0A')", "└── Node('/root/sub1')", " ├── Node('/root/sub1/sub1A')", " ├── Node('/root/sub1/sub1B')", " └── Node('/root/sub1/sub1C')", " └── Node('/root/sub1/sub1C/sub1Ca')", ] anytree-2.12.1/tests/test_dotexport.py000066400000000000000000000043141452550712300200550ustar00rootroot00000000000000from filecmp import cmp from os import makedirs from os.path import dirname, exists, join from shutil import rmtree from anytree import Node from anytree.dotexport import RenderTreeGraph from .helper import with_setup TESTPATH = dirname(__file__) GENPATH = join(TESTPATH, "dotexport") REFPATH = join(TESTPATH, "refdata") def setup(): if not exists(GENPATH): makedirs(GENPATH) def teardown(): if exists(GENPATH): rmtree(GENPATH) @with_setup(setup, teardown) def test_tree1(): """Tree1.""" root = Node("root") s0 = Node("sub0", parent=root) Node("sub0B", parent=s0) Node("sub0A", parent=s0) s1 = Node("sub1", parent=root) Node("sub1A", parent=s1) Node("sub1B", parent=s1) s1c = Node("sub1C", parent=s1) Node(99, parent=s1c) RenderTreeGraph(root).to_dotfile(join(GENPATH, "tree1.dot")) assert cmp(join(GENPATH, "tree1.dot"), join(REFPATH, "tree1.dot")) @with_setup(setup, teardown) def test_tree2(): """Tree2.""" root = Node("root") s0 = Node("sub0", parent=root, edge=2) Node("sub0B", parent=s0, foo=4, edge=109) Node("sub0A", parent=s0, edge="") s1 = Node("sub1", parent=root, edge="") Node("sub1A", parent=s1, edge=7) Node("sub1B", parent=s1, edge=8) s1c = Node("sub1C", parent=s1, edge=22) Node("sub1Ca", parent=s1c, edge=42) def nodenamefunc(node): return "%s:%s" % (node.name, node.depth) def edgeattrfunc(node, child): return 'label="%s:%s"' % (node.name, child.name) r = RenderTreeGraph( root, options=["rankdir=LR;"], nodenamefunc=nodenamefunc, nodeattrfunc=lambda node: "shape=box", edgeattrfunc=edgeattrfunc, ) r.to_dotfile(join(GENPATH, "tree2.dot")) assert cmp(join(GENPATH, "tree2.dot"), join(REFPATH, "tree2.dot")) @with_setup(setup, teardown) def test_tree_png(): """Tree to png.""" root = Node("root") s0 = Node("sub0", parent=root) Node("sub0B", parent=s0) Node("sub0A", parent=s0) s1 = Node("sub1", parent=root) Node("sub1A", parent=s1) Node("sub1B", parent=s1) s1c = Node("sub1C", parent=s1) Node("sub1Ca", parent=s1c) RenderTreeGraph(root).to_picture(join(GENPATH, "tree1.png")) anytree-2.12.1/tests/test_dotexporter.py000066400000000000000000000043311452550712300204030ustar00rootroot00000000000000# -*- coding: utf-8 -*- import pathlib from pytest import fixture from anytree import Node from anytree.exporter import DotExporter REFDATA = pathlib.Path(__file__).parent / "refdata" / "test_dotexporter" from .util import assert_gen @fixture def root(): root = Node("root") s0 = Node("sub0", parent=root, edge=2) Node("sub0B", parent=s0, foo=4, edge=109) Node("sub0A", parent=s0, edge="") s1 = Node("sub1", parent=root, edge="") Node("sub1A", parent=s1, edge=7) Node('sub1"B', parent=s1, edge=8) s1c = Node("su\\b1C", parent=s1, edge=22) Node("sub1Ca", parent=s1c, edge=42) yield root def test_tree(tmp_path, root): """Tree.""" DotExporter(root).to_dotfile(tmp_path / "tree.dot") assert_gen(tmp_path, REFDATA / "tree") def test_tree_custom(tmp_path, root): """Tree Custom.""" def nodenamefunc(node): return "%s:%s" % (node.name, node.depth) def edgeattrfunc(node, child): return 'label="%s:%s"' % (node.name, child.name) def nodefunc(node): return '("%s")' % (node.name) def edgefunc(node, child): return f"--{child.edge}-->" DotExporter( root, options=["rankdir=LR;"], nodenamefunc=nodenamefunc, nodeattrfunc=lambda node: "shape=box", edgeattrfunc=edgeattrfunc, ).to_dotfile(tmp_path / "tree_custom.dot") assert_gen(tmp_path, REFDATA / "tree_custom") def test_tree_filter(tmp_path, root): """Tree with Filter.""" DotExporter(root, filter_=lambda node: node.name.startswith("sub")).to_dotfile(tmp_path / "tree_filter.dot") assert_gen(tmp_path, REFDATA / "tree_filter") def test_tree_stop(tmp_path, root): """Tree with stop.""" DotExporter(root, stop=lambda node: node.name == "sub1").to_dotfile(tmp_path / "tree_stop.dot") assert_gen(tmp_path, REFDATA / "tree_stop") def test_tree_maxlevel(tmp_path, root): """Tree with maxlevel.""" DotExporter(root, maxlevel=2).to_dotfile(tmp_path / "tree_maxlevel.dot") assert_gen(tmp_path, REFDATA / "tree_maxlevel") def test_esc(): """Test proper escape of quotes.""" n = Node(r'6"-6\"') assert tuple(DotExporter(n)) == ( r"digraph tree {", r' "6\"-6\\\"";', r"}", ) anytree-2.12.1/tests/test_examples.py000066400000000000000000000015411452550712300176420ustar00rootroot00000000000000# -*- coding: utf-8 -*- from anytree import Node, RenderTree from .helper import eq_ def test_stackoverflow(): """Example from stackoverflow.""" udo = Node("Udo") marc = Node("Marc", parent=udo) Node("Lian", parent=marc) dan = Node("Dan", parent=udo) Node("Jet", parent=dan) Node("Jan", parent=dan) joe = Node("Joe", parent=dan) eq_(str(udo), "Node('/Udo')") eq_(str(joe), "Node('/Udo/Dan/Joe')") eq_( ["%s%s" % (pre, node.name) for pre, fill, node in RenderTree(udo)], [ "Udo", "├── Marc", "│ └── Lian", "└── Dan", " ├── Jet", " ├── Jan", " └── Joe", ], ) eq_(str(dan.children), "(Node('/Udo/Dan/Jet'), Node('/Udo/Dan/Jan'), Node('/Udo/Dan/Joe'))") anytree-2.12.1/tests/test_iterators.py000066400000000000000000000115521452550712300200430ustar00rootroot00000000000000from anytree import ( LevelGroupOrderIter, LevelOrderGroupIter, LevelOrderIter, Node, PostOrderIter, PreOrderIter, ZigZagGroupIter, ) from .helper import eq_ def test_preorder(): """PreOrderIter.""" f = Node("f") b = Node("b", parent=f) a = Node("a", parent=b) d = Node("d", parent=b) c = Node("c", parent=d) e = Node("e", parent=d) g = Node("g", parent=f) i = Node("i", parent=g) h = Node("h", parent=i) eq_(list(PreOrderIter(f)), [f, b, a, d, c, e, g, i, h]) eq_(list(PreOrderIter(f, maxlevel=0)), []) eq_(list(PreOrderIter(f, maxlevel=3)), [f, b, a, d, g, i]) eq_(list(PreOrderIter(f, filter_=lambda n: n.name not in ("e", "g"))), [f, b, a, d, c, i, h]) eq_(list(PreOrderIter(f, stop=lambda n: n.name == "d")), [f, b, a, g, i, h]) it = PreOrderIter(f) eq_(next(it), f) eq_(next(it), b) eq_(list(it), [a, d, c, e, g, i, h]) def test_postorder(): """PostOrderIter.""" f = Node("f") b = Node("b", parent=f) a = Node("a", parent=b) d = Node("d", parent=b) c = Node("c", parent=d) e = Node("e", parent=d) g = Node("g", parent=f) i = Node("i", parent=g) h = Node("h", parent=i) eq_(list(PostOrderIter(f)), [a, c, e, d, b, h, i, g, f]) eq_(list(PostOrderIter(f, maxlevel=0)), []) eq_(list(PostOrderIter(f, maxlevel=3)), [a, d, b, i, g, f]) eq_(list(PostOrderIter(f, filter_=lambda n: n.name not in ("e", "g"))), [a, c, d, b, h, i, f]) eq_(list(PostOrderIter(f, stop=lambda n: n.name == "d")), [a, b, h, i, g, f]) it = PostOrderIter(f) eq_(next(it), a) eq_(next(it), c) eq_(list(it), [e, d, b, h, i, g, f]) def test_levelorder(): """LevelOrderIter.""" f = Node("f") b = Node("b", parent=f) a = Node("a", parent=b) d = Node("d", parent=b) c = Node("c", parent=d) e = Node("e", parent=d) g = Node("g", parent=f) i = Node("i", parent=g) h = Node("h", parent=i) eq_(list(LevelOrderIter(f)), [f, b, g, a, d, i, c, e, h]) eq_(list(LevelOrderIter(f, maxlevel=0)), []) eq_(list(LevelOrderIter(f, maxlevel=3)), [f, b, g, a, d, i]) eq_(list(LevelOrderIter(f, filter_=lambda n: n.name not in ("e", "g"))), [f, b, a, d, i, c, h]) eq_(list(LevelOrderIter(f, stop=lambda n: n.name == "d")), [f, b, g, a, i, h]) it = LevelOrderIter(f) eq_(next(it), f) eq_(next(it), b) eq_(list(it), [g, a, d, i, c, e, h]) def test_levelgrouporder(): """LevelGroupOrderIter.""" f = Node("f") b = Node("b", parent=f) a = Node("a", parent=b) d = Node("d", parent=b) c = Node("c", parent=d) e = Node("e", parent=d) g = Node("g", parent=f) i = Node("i", parent=g) h = Node("h", parent=i) eq_(list(LevelGroupOrderIter(f)), [(f,), (b, g), (a, d, i), (c, e, h)]) eq_(list(LevelGroupOrderIter(f, maxlevel=0)), []) eq_(list(LevelGroupOrderIter(f, maxlevel=3)), [(f,), (b, g), (a, d, i)]) eq_(list(LevelGroupOrderIter(f, filter_=lambda n: n.name not in ("e", "g"))), [(f,), (b,), (a, d, i), (c, h)]) eq_(list(LevelGroupOrderIter(f, stop=lambda n: n.name == "d")), [(f,), (b, g), (a, i), (h,)]) it = LevelGroupOrderIter(f) eq_(next(it), (f,)) eq_(next(it), (b, g)) eq_(list(it), [(a, d, i), (c, e, h)]) def test_levelordergroup(): """LevelOrderGroupIter.""" f = Node("f") b = Node("b", parent=f) a = Node("a", parent=b) d = Node("d", parent=b) c = Node("c", parent=d) e = Node("e", parent=d) g = Node("g", parent=f) i = Node("i", parent=g) h = Node("h", parent=i) eq_(list(LevelOrderGroupIter(f)), [(f,), (b, g), (a, d, i), (c, e, h)]) eq_(list(LevelOrderGroupIter(f, maxlevel=0)), []) eq_(list(LevelOrderGroupIter(f, maxlevel=3)), [(f,), (b, g), (a, d, i)]) eq_(list(LevelOrderGroupIter(f, filter_=lambda n: n.name not in ("e", "g"))), [(f,), (b,), (a, d, i), (c, h)]) eq_(list(LevelOrderGroupIter(f, stop=lambda n: n.name == "d")), [(f,), (b, g), (a, i), (h,)]) it = LevelOrderGroupIter(f) eq_(next(it), (f,)) eq_(next(it), (b, g)) eq_(list(it), [(a, d, i), (c, e, h)]) def test_zigzaggroup(): """ZigZagGroupIter.""" f = Node("f") b = Node("b", parent=f) a = Node("a", parent=b) d = Node("d", parent=b) c = Node("c", parent=d) e = Node("e", parent=d) g = Node("g", parent=f) i = Node("i", parent=g) h = Node("h", parent=i) eq_(list(ZigZagGroupIter(f)), [(f,), (g, b), (a, d, i), (h, e, c)]) eq_(list(ZigZagGroupIter(f, maxlevel=0)), []) eq_(list(ZigZagGroupIter(f, maxlevel=3)), [(f,), (g, b), (a, d, i)]) eq_(list(ZigZagGroupIter(f, filter_=lambda n: n.name not in ("e", "g"))), [(f,), (b,), (a, d, i), (h, c)]) eq_(list(ZigZagGroupIter(f, stop=lambda n: n.name == "d")), [(f,), (g, b), (a, i), (h,)]) it = ZigZagGroupIter(f) eq_(next(it), (f,)) eq_(next(it), (g, b)) eq_(list(it), [(a, d, i), (h, e, c)]) anytree-2.12.1/tests/test_jsonexporter.py000066400000000000000000000047541452550712300205770ustar00rootroot00000000000000import filecmp import os from tempfile import NamedTemporaryFile from anytree import AnyNode from anytree.exporter import JsonExporter from .helper import eq_ def test_json_exporter(): """Json Exporter.""" root = AnyNode(id="root") s0 = AnyNode(id="sub0", parent=root) AnyNode(id="sub0B", parent=s0) AnyNode(id="sub0A", parent=s0) s1 = AnyNode(id="sub1", parent=root) AnyNode(id="sub1A", parent=s1) AnyNode(id="sub1B", parent=s1) s1c = AnyNode(id="sub1C", parent=s1) AnyNode(id="sub1Ca", parent=s1c) exporter = JsonExporter(indent=2, sort_keys=True) exported = exporter.export(root).splitlines() exported = [e.rstrip() for e in exported] # just a fix for a strange py2x behavior. lines = [ "{", ' "children": [', " {", ' "children": [', " {", ' "id": "sub0B"', " },", " {", ' "id": "sub0A"', " }", " ],", ' "id": "sub0"', " },", " {", ' "children": [', " {", ' "id": "sub1A"', " },", " {", ' "id": "sub1B"', " },", " {", ' "children": [', " {", ' "id": "sub1Ca"', " }", " ],", ' "id": "sub1C"', " }", " ],", ' "id": "sub1"', " }", " ],", ' "id": "root"', "}", ] eq_(exported, lines) exporter = JsonExporter(indent=2, sort_keys=True, maxlevel=2) exported = exporter.export(root).splitlines() exported = [e.rstrip() for e in exported] # just a fix for a strange py2x behavior. limitedlines = [ "{", ' "children": [', " {", ' "id": "sub0"', " },", " {", ' "id": "sub1"', " }", " ],", ' "id": "root"', "}", ] eq_(exported, limitedlines) try: with NamedTemporaryFile(mode="w+", delete=False) as ref: with NamedTemporaryFile(mode="w+", delete=False) as gen: ref.write("\n".join(lines)) exporter.write(root, gen) # on Windows, you must close the files before comparison filecmp.cmp(ref.name, gen.name) finally: os.remove(ref.name) os.remove(gen.name) anytree-2.12.1/tests/test_jsonimporter.py000066400000000000000000000032631452550712300205620ustar00rootroot00000000000000from tempfile import NamedTemporaryFile from anytree.exporter import DictExporter from anytree.importer import JsonImporter from .helper import eq_ def test_json_importer(): """Json Importer.""" refdata = { "id": "root", "children": [ {"id": "sub0", "children": [{"id": "sub0B"}, {"id": "sub0A"}]}, { "id": "sub1", "children": [{"id": "sub1A"}, {"id": "sub1B"}, {"id": "sub1C", "children": [{"id": "sub1Ca"}]}], }, ], } lines = [ "{", ' "children": [', " {", ' "children": [', " {", ' "id": "sub0B"', " },", " {", ' "id": "sub0A"', " }", " ],", ' "id": "sub0"', " },", " {", ' "children": [', " {", ' "id": "sub1A"', " },", " {", ' "id": "sub1B"', " },", " {", ' "children": [', " {", ' "id": "sub1Ca"', " }", " ],", ' "id": "sub1C"', " }", " ],", ' "id": "sub1"', " }", " ],", ' "id": "root"', "}", ] imported = DictExporter().export(JsonImporter().import_("\n".join(lines))) eq_(refdata, imported) with NamedTemporaryFile(mode="w+") as ref: ref.write("\n".join(lines)) ref.seek(0) imported = DictExporter().export(JsonImporter().read(ref)) eq_(refdata, imported) anytree-2.12.1/tests/test_lightnode.py000066400000000000000000000355071452550712300200120ustar00rootroot00000000000000# -*- coding: utf-8 -*- from anytree import LightNodeMixin, LoopError, PostOrderIter, PreOrderIter, TreeError from anytree.node.util import _repr from .helper import assert_raises class LightNode(LightNodeMixin): __slots__ = ["name"] def __init__(self, name, parent=None, children=None): self.name = name self.parent = parent if children: self.children = children def __repr__(self): path = self.separator.join([""] + [str(node.name) for node in self.path]) return "%s(%r)" % (self.__class__.__name__, path) def test_parent_child(): """A tree parent and child attributes.""" root = LightNode("root") s0 = LightNode("sub0", parent=root) s0b = LightNode("sub0B", parent=s0) s0a = LightNode("sub0A", parent=s0) s1 = LightNode("sub1", parent=root) s1a = LightNode("sub1A", parent=s1) s1b = LightNode("sub1B", parent=s1) s1c = LightNode("sub1C", parent=s1) s1ca = LightNode("sub1Ca", parent=s1c) assert root.parent is None assert root.children == tuple([s0, s1]) assert s0.parent == root assert s0.children == tuple([s0b, s0a]) assert s0b.parent == s0 assert s0b.children == tuple() assert s0a.parent == s0 assert s0a.children == tuple() assert s1.parent == root assert s1.children == tuple([s1a, s1b, s1c]) assert s1a.parent == s1 assert s1a.children == tuple() assert s1b.parent == s1 assert s1b.children == tuple() assert s1c.parent == s1 assert s1c.children == tuple([s1ca]) assert s1ca.parent == s1c assert s1ca.children == tuple() # change parent s1ca.parent = s0 assert root.parent is None assert root.children == tuple([s0, s1]) assert s0.parent == root assert s0.children == tuple([s0b, s0a, s1ca]) assert s0b.parent == s0 assert s0b.children == tuple() assert s0a.parent == s0 assert s0a.children == tuple() assert s1.parent == root assert s1.children == tuple([s1a, s1b, s1c]) assert s1a.parent == s1 assert s1a.children == tuple() assert s1b.parent == s1 assert s1b.children == tuple() assert s1c.parent == s1 assert s1c.children == tuple() assert s1ca.parent == s0 assert s1ca.children == tuple() # break tree into two s1.parent = None assert root.parent is None assert root.children == tuple([s0]) assert s0.parent == root assert s0.children == tuple([s0b, s0a, s1ca]) assert s0b.parent == s0 assert s0b.children == tuple() assert s0a.parent == s0 assert s0a.children == tuple() assert s1.parent is None assert s1.children == tuple([s1a, s1b, s1c]) assert s1a.parent == s1 assert s1a.children == tuple() assert s1b.parent == s1 assert s1b.children == tuple() assert s1c.parent == s1 assert s1c.children == tuple() assert s1ca.parent == s0 assert s1ca.children == tuple() # set to the same s1b.parent = s1 assert root.parent is None assert root.children == tuple([s0]) assert s0.parent == root assert s0.children == tuple([s0b, s0a, s1ca]) assert s0b.parent == s0 assert s0b.children == tuple() assert s0a.parent == s0 assert s0a.children == tuple() assert s1.parent is None assert s1.children == tuple([s1a, s1b, s1c]) assert s1a.parent == s1 assert s1a.children == tuple() assert s1b.parent == s1 assert s1b.children == tuple() assert s1c.parent == s1 assert s1c.children == tuple() assert s1ca.parent == s0 assert s1ca.children == tuple() def test_detach_children(): root = LightNode("root") s0 = LightNode("sub0", parent=root) s0b = LightNode("sub0B", parent=s0) s0a = LightNode("sub0A", parent=s0) s1 = LightNode("sub1", parent=root) s1a = LightNode("sub1A", parent=s1) s1b = LightNode("sub1B", parent=s1) s1c = LightNode("sub1C", parent=s1) s1ca = LightNode("sub1Ca", parent=s1c) assert root.descendants == (s0, s0b, s0a, s1, s1a, s1b, s1c, s1ca) del s0.children assert root.descendants == (s0, s1, s1a, s1b, s1c, s1ca) del s1.children assert root.descendants == (s0, s1) def test_children_setter(): root = LightNode("root") s0 = LightNode("sub0") s1 = LightNode("sub0A") s0a = LightNode("sub0B") root.children = [s0, s1] s0.children = [s0a] assert root.descendants == (s0, s0a, s1) with assert_raises(LoopError, "Cannot set parent. LightNode('/root/sub0') cannot be parent of itself."): s0.children = [s0] # test whether tree is unchanged after LoopError assert root.descendants == (s0, s0a, s1) with assert_raises( LoopError, "Cannot set parent. LightNode('/root/sub0') is parent of LightNode('/root/sub0/sub0B')." ): s0a.children = [s0] # test whether tree is unchanged after LoopError assert root.descendants == (s0, s0a, s1) root.children = [s0, s1] s0.children = [s0a] s0a.children = [s1] assert root.descendants == (s0, s0a, s1) def test_children_setter_large(): root = LightNode("root") s0 = LightNode("sub0") s0b = LightNode("sub0B") s0a = LightNode("sub0A") s1 = LightNode("sub1") s1a = LightNode("sub1A") s1b = LightNode("sub1B") s1c = LightNode("sub1C") s1ca = LightNode("sub1Ca") root.children = [s0, s1] assert root.descendants == (s0, s1) s0.children = [s0a, s0b] assert root.descendants == (s0, s0a, s0b, s1) s1.children = [s1a, s1b, s1c] assert root.descendants == (s0, s0a, s0b, s1, s1a, s1b, s1c) with assert_raises(TypeError, "'LightNode' object is not iterable"): s1.children = s1ca assert root.descendants == (s0, s0a, s0b, s1, s1a, s1b, s1c) def test_node_children_multiple(): root = LightNode("root") sub = LightNode("sub") with assert_raises(TreeError, "Cannot add node LightNode('/sub') multiple times as child."): root.children = [sub, sub] def test_recursion_detection(): """Recursion detection.""" root = LightNode("root") s0 = LightNode("sub0", parent=root) LightNode("sub0B", parent=s0) s0a = LightNode("sub0A", parent=s0) # try recursion assert root.parent is None try: root.parent = root except LoopError as exc: assert str(exc) == "Cannot set parent. LightNode('/root') cannot be parent of itself." assert root.parent is None else: assert False assert root.parent is None try: root.parent = s0a except LoopError as exc: assert str(exc) == ("Cannot set parent. LightNode('/root') is parent of LightNode('/root/sub0/sub0A').") assert root.parent is None else: assert False assert s0.parent is root try: s0.parent = s0a except LoopError as exc: assert str(exc) == ("Cannot set parent. LightNode('/root/sub0') is parent of LightNode('/root/sub0/sub0A').") assert s0.parent is root else: assert False def test_ancestors(): """Node.ancestors.""" root = LightNode("root") s0 = LightNode("sub0", parent=root) s0b = LightNode("sub0B", parent=s0) s0a = LightNode("sub0A", parent=s0) s1 = LightNode("sub1", parent=root) s1c = LightNode("sub1C", parent=s1) s1ca = LightNode("sub1Ca", parent=s1c) assert root.ancestors == tuple() assert s0.ancestors == tuple([root]) assert s0b.ancestors == tuple([root, s0]) assert s0a.ancestors == tuple([root, s0]) assert s1ca.ancestors == tuple([root, s1, s1c]) def test_node_children_init(): """Node With Children Attribute.""" root = LightNode("root", children=[LightNode("a", children=[LightNode("aa")]), LightNode("b")]) assert repr(root.descendants) == "(LightNode('/root/a'), LightNode('/root/a/aa'), LightNode('/root/b'))" def test_descendants(): """Node.descendants.""" root = LightNode("root") s0 = LightNode("sub0", parent=root) s0b = LightNode("sub0B", parent=s0) s0a = LightNode("sub0A", parent=s0) s1 = LightNode("sub1", parent=root) s1c = LightNode("sub1C", parent=s1) s1ca = LightNode("sub1Ca", parent=s1c) assert root.descendants == tuple([s0, s0b, s0a, s1, s1c, s1ca]) assert s1.descendants == tuple([s1c, s1ca]) assert s1c.descendants == tuple([s1ca]) assert s1ca.descendants == tuple() def test_root(): """Node.root.""" root = LightNode("root") s0 = LightNode("sub0", parent=root) s0b = LightNode("sub0B", parent=s0) s0a = LightNode("sub0A", parent=s0) s1 = LightNode("sub1", parent=root) s1c = LightNode("sub1C", parent=s1) s1ca = LightNode("sub1Ca", parent=s1c) assert root.root == root assert s0.root == root assert s0b.root == root assert s0a.root == root assert s1.root == root assert s1c.root == root assert s1ca.root == root def test_siblings(): """Node.siblings.""" root = LightNode("root") s0 = LightNode("sub0", parent=root) s0b = LightNode("sub0B", parent=s0) s0a = LightNode("sub0A", parent=s0) s1 = LightNode("sub1", parent=root) s1c = LightNode("sub1C", parent=s1) s1ca = LightNode("sub1Ca", parent=s1c) assert root.siblings == tuple() assert s0.siblings == tuple([s1]) assert s0b.siblings == tuple([s0a]) assert s0a.siblings == tuple([s0b]) assert s1.siblings == tuple([s0]) assert s1c.siblings == tuple() assert s1ca.siblings == tuple() def test_is_leaf(): """Node.is_leaf.""" root = LightNode("root") s0 = LightNode("sub0", parent=root) s0b = LightNode("sub0B", parent=s0) s0a = LightNode("sub0A", parent=s0) s1 = LightNode("sub1", parent=root) s1c = LightNode("sub1C", parent=s1) s1ca = LightNode("sub1Ca", parent=s1c) assert root.is_leaf is False assert s0.is_leaf is False assert s0b.is_leaf is True assert s0a.is_leaf is True assert s1.is_leaf is False assert s1c.is_leaf is False assert s1ca.is_leaf is True def test_leaves(): """Node.leaves.""" root = LightNode("root") s0 = LightNode("sub0", parent=root) s0b = LightNode("sub0B", parent=s0) s0a = LightNode("sub0A", parent=s0) s1 = LightNode("sub1", parent=root) s1c = LightNode("sub1C", parent=s1) s1ca = LightNode("sub1Ca", parent=s1c) assert root.leaves == tuple([s0b, s0a, s1ca]) assert s0.leaves == tuple([s0b, s0a]) assert s0b.leaves == tuple([s0b]) assert s0a.leaves == tuple([s0a]) assert s1.leaves == tuple([s1ca]) assert s1c.leaves == tuple([s1ca]) assert s1ca.leaves == tuple([s1ca]) def test_is_root(): """Node.is_root.""" root = LightNode("root") s0 = LightNode("sub0", parent=root) s0b = LightNode("sub0B", parent=s0) s0a = LightNode("sub0A", parent=s0) s1 = LightNode("sub1", parent=root) s1c = LightNode("sub1C", parent=s1) s1ca = LightNode("sub1Ca", parent=s1c) assert root.is_root is True assert s0.is_root is False assert s0b.is_root is False assert s0a.is_root is False assert s1.is_root is False assert s1c.is_root is False assert s1ca.is_root is False def test_height(): """Node.height.""" root = LightNode("root") s0 = LightNode("sub0", parent=root) s0b = LightNode("sub0B", parent=s0) s0a = LightNode("sub0A", parent=s0) s1 = LightNode("sub1", parent=root) s1c = LightNode("sub1C", parent=s1) s1ca = LightNode("sub1Ca", parent=s1c) assert root.height == 3 assert s0.height == 1 assert s0b.height == 0 assert s0a.height == 0 assert s1.height == 2 assert s1c.height == 1 assert s1ca.height == 0 def test_size(): """Node.size.""" root = LightNode("root") s0 = LightNode("sub0", parent=root) s0b = LightNode("sub0B", parent=s0) s0a = LightNode("sub0A", parent=s0) s1 = LightNode("sub1", parent=root) s1c = LightNode("sub1C", parent=s1) s1ca = LightNode("sub1Ca", parent=s1c) assert root.size == 7 assert s0.size == 3 assert s0b.size == 1 assert s0a.size == 1 assert s1.size == 3 assert s1c.size == 2 assert s1ca.size == 1 def test_size(): """Node.size.""" root = LightNode("root") s0 = LightNode("sub0", parent=root) s0b = LightNode("sub0B", parent=s0) s0a = LightNode("sub0A", parent=s0) s1 = LightNode("sub1", parent=root) s1c = LightNode("sub1C", parent=s1) s1ca = LightNode("sub1Ca", parent=s1c) assert root.size == 7 assert s0.size == 3 assert s0b.size == 1 assert s0a.size == 1 assert s1.size == 3 assert s1c.size == 2 assert s1ca.size == 1 def test_depth(): """Node.depth.""" root = LightNode("root") s0 = LightNode("sub0", parent=root) s0b = LightNode("sub0B", parent=s0) s0a = LightNode("sub0A", parent=s0) s1 = LightNode("sub1", parent=root) s1c = LightNode("sub1C", parent=s1) s1ca = LightNode("sub1Ca", parent=s1c) assert root.depth == 0 assert s0.depth == 1 assert s0b.depth == 2 assert s0a.depth == 2 assert s1.depth == 1 assert s1c.depth == 2 assert s1ca.depth == 3 def test_parent(): """Parent attribute.""" foo = LightNodeMixin() assert foo.parent is None def test_pre_order_iter(): """Pre-Order Iterator.""" f = LightNode("f") b = LightNode("b", parent=f) a = LightNode("a", parent=b) d = LightNode("d", parent=b) c = LightNode("c", parent=d) e = LightNode("e", parent=d) g = LightNode("g", parent=f) i = LightNode("i", parent=g) h = LightNode("h", parent=i) assert [node.name for node in PreOrderIter(f)] == ["f", "b", "a", "d", "c", "e", "g", "i", "h"] def test_post_order_iter(): """Post-Order Iterator.""" f = LightNode("f") b = LightNode("b", parent=f) a = LightNode("a", parent=b) d = LightNode("d", parent=b) c = LightNode("c", parent=d) e = LightNode("e", parent=d) g = LightNode("g", parent=f) i = LightNode("i", parent=g) h = LightNode("h", parent=i) assert [node.name for node in PostOrderIter(f)] == ["a", "c", "e", "d", "b", "h", "i", "g", "f"] def test_hookups(): """Hookup attributes #29.""" class MyLightNode(LightNode): def _pre_attach(self, parent): assert str(self.parent) == "None" assert self.children == tuple() assert str(self.path) == "(MyLightNode('/B'),)" def _post_attach(self, parent): assert str(self.parent) == "MyLightNode('/A')" assert self.children == tuple() assert str(self.path) == "(MyLightNode('/A'), MyLightNode('/A/B'))" def _pre_detach(self, parent): assert str(self.parent) == "MyLightNode('/A')" assert self.children == tuple() assert str(self.path) == "(MyLightNode('/A'), MyLightNode('/A/B'))" def _post_detach(self, parent): assert str(self.parent) == "None" assert self.children == tuple() assert str(self.path) == "(MyLightNode('/B'),)" node_a = MyLightNode("A") node_b = MyLightNode("B", node_a) # attach B on A node_b.parent = None # detach B from A anytree-2.12.1/tests/test_mermaidexporter.py000066400000000000000000000036151452550712300212370ustar00rootroot00000000000000# -*- coding: utf-8 -*- import pathlib from pytest import fixture from anytree import Node from anytree.exporter import MermaidExporter REFDATA = pathlib.Path(__file__).parent / "refdata" / "test_mermaidexporter" from .util import assert_gen @fixture def root(): root = Node("root") s0 = Node("sub0", parent=root, edge=2) Node("sub0B", parent=s0, foo=4, edge=109) Node("sub0A", parent=s0, edge="") s1 = Node("sub1", parent=root, edge="") Node("sub1A", parent=s1, edge=7) Node('sub1"B', parent=s1, edge=8) s1c = Node("su\\b1C", parent=s1, edge=22) Node("sub1Ca", parent=s1c, edge=42) yield root def test_tree(tmp_path, root): """Tree.""" MermaidExporter(root).to_file(tmp_path / "tree.md") assert_gen(tmp_path, REFDATA / "tree") def test_tree_custom(tmp_path, root): """Tree Custom.""" def nodefunc(node): return '("%s")' % (node.name) def edgefunc(node, child): return f"--{child.edge}-->" options = [ "%% just an example comment", "%% could be an option too", ] MermaidExporter( root, options=options, nodefunc=nodefunc, edgefunc=edgefunc, ).to_file(tmp_path / "tree_custom.md") assert_gen(tmp_path, REFDATA / "tree_custom") def test_tree_filter(tmp_path, root): """Tree with Filter.""" MermaidExporter(root, filter_=lambda node: node.name.startswith("sub")).to_file(tmp_path / "tree_filter.md") assert_gen(tmp_path, REFDATA / "tree_filter") def test_tree_stop(tmp_path, root): """Tree with stop.""" MermaidExporter(root, stop=lambda node: node.name == "sub1").to_file(tmp_path / "tree_stop.md") assert_gen(tmp_path, REFDATA / "tree_stop") def test_tree_maxlevel(tmp_path, root): """Tree with maxlevel.""" MermaidExporter(root, maxlevel=2).to_file(tmp_path / "tree_maxlevel.md") assert_gen(tmp_path, REFDATA / "tree_maxlevel") anytree-2.12.1/tests/test_node.py000066400000000000000000000415361452550712300167610ustar00rootroot00000000000000# -*- coding: utf-8 -*- from anytree import AnyNode, LoopError, Node, NodeMixin, PostOrderIter, PreOrderIter, TreeError from .helper import assert_raises def test_node_parent_error(): """Node Parent Error.""" with assert_raises(TreeError, "Parent node 'parent' is not of type 'NodeMixin'."): Node("root", "parent") def test_parent_child(): """A tree parent and child attributes.""" root = Node("root") s0 = Node("sub0", parent=root) s0b = Node("sub0B", parent=s0) s0a = Node("sub0A", parent=s0) s1 = Node("sub1", parent=root) s1a = Node("sub1A", parent=s1) s1b = Node("sub1B", parent=s1) s1c = Node("sub1C", parent=s1) s1ca = Node("sub1Ca", parent=s1c) assert root.parent is None assert root.children == tuple([s0, s1]) assert s0.parent == root assert s0.children == tuple([s0b, s0a]) assert s0b.parent == s0 assert s0b.children == tuple() assert s0a.parent == s0 assert s0a.children == tuple() assert s1.parent == root assert s1.children == tuple([s1a, s1b, s1c]) assert s1a.parent == s1 assert s1a.children == tuple() assert s1b.parent == s1 assert s1b.children == tuple() assert s1c.parent == s1 assert s1c.children == tuple([s1ca]) assert s1ca.parent == s1c assert s1ca.children == tuple() # change parent s1ca.parent = s0 assert root.parent is None assert root.children == tuple([s0, s1]) assert s0.parent == root assert s0.children == tuple([s0b, s0a, s1ca]) assert s0b.parent == s0 assert s0b.children == tuple() assert s0a.parent == s0 assert s0a.children == tuple() assert s1.parent == root assert s1.children == tuple([s1a, s1b, s1c]) assert s1a.parent == s1 assert s1a.children == tuple() assert s1b.parent == s1 assert s1b.children == tuple() assert s1c.parent == s1 assert s1c.children == tuple() assert s1ca.parent == s0 assert s1ca.children == tuple() # break tree into two s1.parent = None assert root.parent is None assert root.children == tuple([s0]) assert s0.parent == root assert s0.children == tuple([s0b, s0a, s1ca]) assert s0b.parent == s0 assert s0b.children == tuple() assert s0a.parent == s0 assert s0a.children == tuple() assert s1.parent is None assert s1.children == tuple([s1a, s1b, s1c]) assert s1a.parent == s1 assert s1a.children == tuple() assert s1b.parent == s1 assert s1b.children == tuple() assert s1c.parent == s1 assert s1c.children == tuple() assert s1ca.parent == s0 assert s1ca.children == tuple() # set to the same s1b.parent = s1 assert root.parent is None assert root.children == tuple([s0]) assert s0.parent == root assert s0.children == tuple([s0b, s0a, s1ca]) assert s0b.parent == s0 assert s0b.children == tuple() assert s0a.parent == s0 assert s0a.children == tuple() assert s1.parent is None assert s1.children == tuple([s1a, s1b, s1c]) assert s1a.parent == s1 assert s1a.children == tuple() assert s1b.parent == s1 assert s1b.children == tuple() assert s1c.parent == s1 assert s1c.children == tuple() assert s1ca.parent == s0 assert s1ca.children == tuple() def test_detach_children(): root = Node("root") s0 = Node("sub0", parent=root) s0b = Node("sub0B", parent=s0) s0a = Node("sub0A", parent=s0) s1 = Node("sub1", parent=root) s1a = Node("sub1A", parent=s1) s1b = Node("sub1B", parent=s1) s1c = Node("sub1C", parent=s1) s1ca = Node("sub1Ca", parent=s1c) assert root.descendants == (s0, s0b, s0a, s1, s1a, s1b, s1c, s1ca) del s0.children assert root.descendants == (s0, s1, s1a, s1b, s1c, s1ca) del s1.children assert root.descendants == (s0, s1) def test_children_setter(): root = Node("root") s0 = Node("sub0") s1 = Node("sub0A") s0a = Node("sub0B") root.children = [s0, s1] s0.children = [s0a] assert root.descendants == (s0, s0a, s1) with assert_raises(LoopError, "Cannot set parent. Node('/root/sub0') cannot be parent of itself."): s0.children = [s0] # test whether tree is unchanged after LoopError assert root.descendants == (s0, s0a, s1) with assert_raises(LoopError, "Cannot set parent. Node('/root/sub0') is parent of Node('/root/sub0/sub0B')."): s0a.children = [s0] # test whether tree is unchanged after LoopError assert root.descendants == (s0, s0a, s1) root.children = [s0, s1] s0.children = [s0a] s0a.children = [s1] assert root.descendants == (s0, s0a, s1) def test_children_setter_large(): root = Node("root") s0 = Node("sub0") s0b = Node("sub0B") s0a = Node("sub0A") s1 = Node("sub1") s1a = Node("sub1A") s1b = Node("sub1B") s1c = Node("sub1C") s1ca = Node("sub1Ca") root.children = [s0, s1] assert root.descendants == (s0, s1) s0.children = [s0a, s0b] assert root.descendants == (s0, s0a, s0b, s1) s1.children = [s1a, s1b, s1c] assert root.descendants == (s0, s0a, s0b, s1, s1a, s1b, s1c) with assert_raises(TypeError, "'Node' object is not iterable"): s1.children = s1ca assert root.descendants == (s0, s0a, s0b, s1, s1a, s1b, s1c) def test_node_children_type(): root = Node("root") with assert_raises(TreeError, "Cannot add non-node object 'string'. It is not a subclass of 'NodeMixin'."): root.children = ["string"] def test_node_children_multiple(): root = Node("root") sub = Node("sub") with assert_raises(TreeError, "Cannot add node Node('/sub') multiple times as child."): root.children = [sub, sub] def test_recursion_detection(): """Recursion detection.""" root = Node("root") s0 = Node("sub0", parent=root) Node("sub0B", parent=s0) s0a = Node("sub0A", parent=s0) # try recursion assert root.parent is None try: root.parent = root except LoopError as exc: assert str(exc) == "Cannot set parent. Node('/root') cannot be parent of itself." assert root.parent is None else: assert False assert root.parent is None try: root.parent = s0a except LoopError as exc: assert str(exc) == ("Cannot set parent. Node('/root') is parent of Node('/root/sub0/sub0A').") assert root.parent is None else: assert False assert s0.parent is root try: s0.parent = s0a except LoopError as exc: assert str(exc) == ("Cannot set parent. Node('/root/sub0') is parent of Node('/root/sub0/sub0A').") assert s0.parent is root else: assert False def test_repr(): """Node representation.""" root = Node("root") s0 = Node("sub0", parent=root) s1 = Node("sub1", parent=root, foo=42, bar="c0fe") assert repr(root) == "Node('/root')" assert repr(s0) == "Node('/root/sub0')" assert repr(s1) == "Node('/root/sub1', bar='c0fe', foo=42)" def test_ancestors(): """Node.ancestors.""" root = Node("root") s0 = Node("sub0", parent=root) s0b = Node("sub0B", parent=s0) s0a = Node("sub0A", parent=s0) s1 = Node("sub1", parent=root) s1c = Node("sub1C", parent=s1) s1ca = Node("sub1Ca", parent=s1c) assert root.ancestors == tuple() assert s0.ancestors == tuple([root]) assert s0b.ancestors == tuple([root, s0]) assert s0a.ancestors == tuple([root, s0]) assert s1ca.ancestors == tuple([root, s1, s1c]) # deprecated typo assert s1ca.anchestors == tuple([root, s1, s1c]) def test_node_children_init(): """Node With Children Attribute.""" root = Node("root", children=[Node("a", children=[Node("aa")]), Node("b")]) assert repr(root.descendants) == "(Node('/root/a'), Node('/root/a/aa'), Node('/root/b'))" def test_anynode_children_init(): """Anynode With Children Attribute.""" root = AnyNode(foo="root", children=[AnyNode(foo="a", children=[AnyNode(foo="aa")]), AnyNode(foo="b")]) assert repr(root.descendants) == "(AnyNode(foo='a'), AnyNode(foo='aa'), AnyNode(foo='b'))" def test_descendants(): """Node.descendants.""" root = Node("root") s0 = Node("sub0", parent=root) s0b = Node("sub0B", parent=s0) s0a = Node("sub0A", parent=s0) s1 = Node("sub1", parent=root) s1c = Node("sub1C", parent=s1) s1ca = Node("sub1Ca", parent=s1c) assert root.descendants == tuple([s0, s0b, s0a, s1, s1c, s1ca]) assert s1.descendants == tuple([s1c, s1ca]) assert s1c.descendants == tuple([s1ca]) assert s1ca.descendants == tuple() def test_root(): """Node.root.""" root = Node("root") s0 = Node("sub0", parent=root) s0b = Node("sub0B", parent=s0) s0a = Node("sub0A", parent=s0) s1 = Node("sub1", parent=root) s1c = Node("sub1C", parent=s1) s1ca = Node("sub1Ca", parent=s1c) assert root.root == root assert s0.root == root assert s0b.root == root assert s0a.root == root assert s1.root == root assert s1c.root == root assert s1ca.root == root def test_siblings(): """Node.siblings.""" root = Node("root") s0 = Node("sub0", parent=root) s0b = Node("sub0B", parent=s0) s0a = Node("sub0A", parent=s0) s1 = Node("sub1", parent=root) s1c = Node("sub1C", parent=s1) s1ca = Node("sub1Ca", parent=s1c) assert root.siblings == tuple() assert s0.siblings == tuple([s1]) assert s0b.siblings == tuple([s0a]) assert s0a.siblings == tuple([s0b]) assert s1.siblings == tuple([s0]) assert s1c.siblings == tuple() assert s1ca.siblings == tuple() def test_is_leaf(): """Node.is_leaf.""" root = Node("root") s0 = Node("sub0", parent=root) s0b = Node("sub0B", parent=s0) s0a = Node("sub0A", parent=s0) s1 = Node("sub1", parent=root) s1c = Node("sub1C", parent=s1) s1ca = Node("sub1Ca", parent=s1c) assert root.is_leaf is False assert s0.is_leaf is False assert s0b.is_leaf is True assert s0a.is_leaf is True assert s1.is_leaf is False assert s1c.is_leaf is False assert s1ca.is_leaf is True def test_leaves(): """Node.leaves.""" root = Node("root") s0 = Node("sub0", parent=root) s0b = Node("sub0B", parent=s0) s0a = Node("sub0A", parent=s0) s1 = Node("sub1", parent=root) s1c = Node("sub1C", parent=s1) s1ca = Node("sub1Ca", parent=s1c) assert root.leaves == tuple([s0b, s0a, s1ca]) assert s0.leaves == tuple([s0b, s0a]) assert s0b.leaves == tuple([s0b]) assert s0a.leaves == tuple([s0a]) assert s1.leaves == tuple([s1ca]) assert s1c.leaves == tuple([s1ca]) assert s1ca.leaves == tuple([s1ca]) def test_is_root(): """Node.is_root.""" root = Node("root") s0 = Node("sub0", parent=root) s0b = Node("sub0B", parent=s0) s0a = Node("sub0A", parent=s0) s1 = Node("sub1", parent=root) s1c = Node("sub1C", parent=s1) s1ca = Node("sub1Ca", parent=s1c) assert root.is_root is True assert s0.is_root is False assert s0b.is_root is False assert s0a.is_root is False assert s1.is_root is False assert s1c.is_root is False assert s1ca.is_root is False def test_height(): """Node.height.""" root = Node("root") s0 = Node("sub0", parent=root) s0b = Node("sub0B", parent=s0) s0a = Node("sub0A", parent=s0) s1 = Node("sub1", parent=root) s1c = Node("sub1C", parent=s1) s1ca = Node("sub1Ca", parent=s1c) assert root.height == 3 assert s0.height == 1 assert s0b.height == 0 assert s0a.height == 0 assert s1.height == 2 assert s1c.height == 1 assert s1ca.height == 0 def test_size(): """Node.size.""" root = Node("root") s0 = Node("sub0", parent=root) s0b = Node("sub0B", parent=s0) s0a = Node("sub0A", parent=s0) s1 = Node("sub1", parent=root) s1c = Node("sub1C", parent=s1) s1ca = Node("sub1Ca", parent=s1c) assert root.size == 7 assert s0.size == 3 assert s0b.size == 1 assert s0a.size == 1 assert s1.size == 3 assert s1c.size == 2 assert s1ca.size == 1 def test_size(): """Node.size.""" root = Node("root") s0 = Node("sub0", parent=root) s0b = Node("sub0B", parent=s0) s0a = Node("sub0A", parent=s0) s1 = Node("sub1", parent=root) s1c = Node("sub1C", parent=s1) s1ca = Node("sub1Ca", parent=s1c) assert root.size == 7 assert s0.size == 3 assert s0b.size == 1 assert s0a.size == 1 assert s1.size == 3 assert s1c.size == 2 assert s1ca.size == 1 def test_depth(): """Node.depth.""" root = Node("root") s0 = Node("sub0", parent=root) s0b = Node("sub0B", parent=s0) s0a = Node("sub0A", parent=s0) s1 = Node("sub1", parent=root) s1c = Node("sub1C", parent=s1) s1ca = Node("sub1Ca", parent=s1c) assert root.depth == 0 assert s0.depth == 1 assert s0b.depth == 2 assert s0a.depth == 2 assert s1.depth == 1 assert s1c.depth == 2 assert s1ca.depth == 3 def test_parent(): """Parent attribute.""" foo = NodeMixin() assert foo.parent is None def test_pre_order_iter(): """Pre-Order Iterator.""" f = Node("f") b = Node("b", parent=f) a = Node("a", parent=b) d = Node("d", parent=b) c = Node("c", parent=d) e = Node("e", parent=d) g = Node("g", parent=f) i = Node("i", parent=g) h = Node("h", parent=i) assert [node.name for node in PreOrderIter(f)] == ["f", "b", "a", "d", "c", "e", "g", "i", "h"] def test_post_order_iter(): """Post-Order Iterator.""" f = Node("f") b = Node("b", parent=f) a = Node("a", parent=b) d = Node("d", parent=b) c = Node("c", parent=d) e = Node("e", parent=d) g = Node("g", parent=f) i = Node("i", parent=g) h = Node("h", parent=i) assert [node.name for node in PostOrderIter(f)] == ["a", "c", "e", "d", "b", "h", "i", "g", "f"] def test_anyname(): """Support any type as name.""" myroot = Node([1, 2, 3]) Node("/foo", parent=myroot) assert str(myroot) == "Node('/[1, 2, 3]')" def test_node_kwargs(): """Ticket #24.""" class MyNode(Node): def __init__(self, name, parent=None, **kwargs): super(MyNode, self).__init__(name, parent, **kwargs) def _post_attach(self, parent): print(self.my_attribute) node_a = MyNode("A") node_b = MyNode("B", node_a, my_attribute=True) assert repr(node_b) == "MyNode('/A/B', my_attribute=True)" def test_hookups(): """Hookup attributes #29.""" class MyNode(Node): def _pre_attach(self, parent): assert str(self.parent) == "None" assert self.children == tuple() assert str(self.path) == "(MyNode('/B'),)" def _post_attach(self, parent): assert str(self.parent) == "MyNode('/A')" assert self.children == tuple() assert str(self.path) == "(MyNode('/A'), MyNode('/A/B'))" def _pre_detach(self, parent): assert str(self.parent) == "MyNode('/A')" assert self.children == tuple() assert str(self.path) == "(MyNode('/A'), MyNode('/A/B'))" def _post_detach(self, parent): assert str(self.parent) == "None" assert self.children == tuple() assert str(self.path) == "(MyNode('/B'),)" node_a = MyNode("A") node_b = MyNode("B", node_a) # attach B on A node_b.parent = None # detach B from A def test_any_node_parent_error(): """Any Node Parent Error.""" with assert_raises(TreeError, "Parent node 'r' is not of type 'NodeMixin'."): AnyNode("r") def test_any_node(): """Any Node.""" r = AnyNode() a = AnyNode() b = AnyNode(foo=4) assert r.parent is None assert a.parent is None assert b.parent is None a.parent = r b.parent = r assert r.children == (a, b) assert repr(r) == "AnyNode()" assert repr(a) == "AnyNode()" assert repr(b) == "AnyNode(foo=4)" def test_eq_overwrite(): """Node with overwritten __eq__.""" class EqOverwrittingNode(NodeMixin): def __init__(self, a, b, parent=None): super(EqOverwrittingNode, self).__init__() self.a = a self.b = b self.parent = parent def __eq__(self, other): if isinstance(other, EqOverwrittingNode): return self.a == other.a and self.b == other.b else: return NotImplemented r = EqOverwrittingNode(0, 0) a = EqOverwrittingNode(1, 0, parent=r) b = EqOverwrittingNode(1, 0, parent=r) assert a.parent is r assert b.parent is r assert a.a == 1 assert a.b == 0 assert b.a == 1 assert b.b == 0 def test_tuple(): """Tuple as parent.""" with assert_raises(TreeError, "Parent node (1, 0, 3) is not of type 'NodeMixin'."): Node((0, 1, 2), parent=(1, 0, 3)) def test_tuple_as_children(): """Tuple as children.""" n = Node("foo") with assert_raises(TreeError, "Cannot add non-node object (0, 1, 2). It is not a subclass of 'NodeMixin'."): n.children = [(0, 1, 2)] anytree-2.12.1/tests/test_node_attach_detach.py000066400000000000000000000230001452550712300215770ustar00rootroot00000000000000# -*- coding: utf-8 -*- from anytree import AnyNode, LoopError, Node, NodeMixin, PostOrderIter, PreOrderIter, RenderTree, TreeError from .helper import assert_raises, eq_ class TNode(Node): TRACKING = [] def _pre_detach(self, parent): """Method call before detaching from `parent`.""" self.TRACKING.append("_pre_detach(%r, %r)" % (self.name, parent.name)) def _post_detach(self, parent): """Method call after detaching from `parent`.""" self.TRACKING.append("_post_detach(%r, %r)" % (self.name, parent.name)) def _pre_attach(self, parent): """Method call before attaching to `parent`.""" self.TRACKING.append("_pre_attach(%r, %r)" % (self.name, parent.name)) def _post_attach(self, parent): """Method call after attaching to `parent`.""" self.TRACKING.append("_post_attach(%r, %r)" % (self.name, parent.name)) def _pre_detach_children(self, children): """Method call before detaching `children`.""" self.TRACKING.append("_pre_detach_children(%r, %r)" % (self.name, tuple(child.name for child in children))) def _post_detach_children(self, children): """Method call after detaching `children`.""" self.TRACKING.append("_post_detach_children(%r, %r)" % (self.name, tuple(child.name for child in children))) def _pre_attach_children(self, children): """Method call before attaching `children`.""" self.TRACKING.append("_pre_attach_children(%r, %r)" % (self.name, tuple(child.name for child in children))) def _post_attach_children(self, children): """Method call after attaching `children`.""" self.TRACKING.append("_post_attach_children(%r, %r)" % (self.name, tuple(child.name for child in children))) def test_parent_child(): """A tree parent and child attributes.""" root = TNode("root") s0 = TNode("sub0", parent=root) s0b = TNode("sub0B", parent=s0) s0a = TNode("sub0A", parent=s0) s1 = TNode("sub1", parent=root) s1a = TNode("sub1A", parent=s1) s1b = TNode("sub1B", parent=s1) s1c = TNode("sub1C", parent=s1) s1ca = TNode("sub1Ca", parent=s1c) assert TNode.TRACKING == [ "_pre_attach('sub0', 'root')", "_post_attach('sub0', 'root')", "_pre_attach('sub0B', 'sub0')", "_post_attach('sub0B', 'sub0')", "_pre_attach('sub0A', 'sub0')", "_post_attach('sub0A', 'sub0')", "_pre_attach('sub1', 'root')", "_post_attach('sub1', 'root')", "_pre_attach('sub1A', 'sub1')", "_post_attach('sub1A', 'sub1')", "_pre_attach('sub1B', 'sub1')", "_post_attach('sub1B', 'sub1')", "_pre_attach('sub1C', 'sub1')", "_post_attach('sub1C', 'sub1')", "_pre_attach('sub1Ca', 'sub1C')", "_post_attach('sub1Ca', 'sub1C')", ] TNode.TRACKING.clear() # change parent s1ca.parent = s0 # break tree into two s1.parent = None # set to the same s1b.parent = s1 assert TNode.TRACKING == [ "_pre_detach('sub1Ca', 'sub1C')", "_post_detach('sub1Ca', 'sub1C')", "_pre_attach('sub1Ca', 'sub0')", "_post_attach('sub1Ca', 'sub0')", "_pre_detach('sub1', 'root')", "_post_detach('sub1', 'root')", ] TNode.TRACKING.clear() def test_detach_children(): root = TNode("root") s0 = TNode("sub0", parent=root) s0b = TNode("sub0B", parent=s0) s0a = TNode("sub0A", parent=s0) s1 = TNode("sub1", parent=root) s1a = TNode("sub1A", parent=s1) s1b = TNode("sub1B", parent=s1) s1c = TNode("sub1C", parent=s1) s1ca = TNode("sub1Ca", parent=s1c) assert TNode.TRACKING == [ "_pre_attach('sub0', 'root')", "_post_attach('sub0', 'root')", "_pre_attach('sub0B', 'sub0')", "_post_attach('sub0B', 'sub0')", "_pre_attach('sub0A', 'sub0')", "_post_attach('sub0A', 'sub0')", "_pre_attach('sub1', 'root')", "_post_attach('sub1', 'root')", "_pre_attach('sub1A', 'sub1')", "_post_attach('sub1A', 'sub1')", "_pre_attach('sub1B', 'sub1')", "_post_attach('sub1B', 'sub1')", "_pre_attach('sub1C', 'sub1')", "_post_attach('sub1C', 'sub1')", "_pre_attach('sub1Ca', 'sub1C')", "_post_attach('sub1Ca', 'sub1C')", ] TNode.TRACKING.clear() del s0.children assert TNode.TRACKING == [ "_pre_detach_children('sub0', ('sub0B', 'sub0A'))", "_pre_detach('sub0B', 'sub0')", "_post_detach('sub0B', 'sub0')", "_pre_detach('sub0A', 'sub0')", "_post_detach('sub0A', 'sub0')", "_post_detach_children('sub0', ('sub0B', 'sub0A'))", ] TNode.TRACKING.clear() del s1.children assert TNode.TRACKING == [ "_pre_detach_children('sub1', ('sub1A', 'sub1B', 'sub1C'))", "_pre_detach('sub1A', 'sub1')", "_post_detach('sub1A', 'sub1')", "_pre_detach('sub1B', 'sub1')", "_post_detach('sub1B', 'sub1')", "_pre_detach('sub1C', 'sub1')", "_post_detach('sub1C', 'sub1')", "_post_detach_children('sub1', ('sub1A', 'sub1B', 'sub1C'))", ] TNode.TRACKING.clear() def test_children_setter(): root = TNode("root") s0 = TNode("sub0") s1 = TNode("sub0A") s0a = TNode("sub0B") assert TNode.TRACKING == [] TNode.TRACKING.clear() root.children = [s0, s1] s0.children = [s0a] assert TNode.TRACKING == [ "_pre_detach_children('root', ())", "_post_detach_children('root', ())", "_pre_attach_children('root', ('sub0', 'sub0A'))", "_pre_attach('sub0', 'root')", "_post_attach('sub0', 'root')", "_pre_attach('sub0A', 'root')", "_post_attach('sub0A', 'root')", "_post_attach_children('root', ('sub0', 'sub0A'))", "_pre_detach_children('sub0', ())", "_post_detach_children('sub0', ())", "_pre_attach_children('sub0', ('sub0B',))", "_pre_attach('sub0B', 'sub0')", "_post_attach('sub0B', 'sub0')", "_post_attach_children('sub0', ('sub0B',))", ] TNode.TRACKING.clear() with assert_raises(LoopError, "Cannot set parent. TNode('/root/sub0') cannot be parent of itself."): s0.children = [s0] # test whether tree is unchanged after LoopError assert TNode.TRACKING == [ "_pre_detach_children('sub0', ('sub0B',))", "_pre_detach('sub0B', 'sub0')", "_post_detach('sub0B', 'sub0')", "_post_detach_children('sub0', ('sub0B',))", "_pre_attach_children('sub0', ('sub0',))", "_pre_detach_children('sub0', ())", "_post_detach_children('sub0', ())", "_pre_attach_children('sub0', ('sub0B',))", "_pre_attach('sub0B', 'sub0')", "_post_attach('sub0B', 'sub0')", "_post_attach_children('sub0', ('sub0B',))", ] TNode.TRACKING.clear() with assert_raises(LoopError, "Cannot set parent. TNode('/root/sub0') is parent of TNode('/root/sub0/sub0B')."): s0a.children = [s0] # test whether tree is unchanged after LoopError assert TNode.TRACKING == [ "_pre_detach_children('sub0B', ())", "_post_detach_children('sub0B', ())", "_pre_attach_children('sub0B', ('sub0',))", "_pre_detach_children('sub0B', ())", "_post_detach_children('sub0B', ())", "_pre_attach_children('sub0B', ())", "_post_attach_children('sub0B', ())", ] TNode.TRACKING.clear() root.children = [s0, s1] assert TNode.TRACKING == [ "_pre_detach_children('root', ('sub0', 'sub0A'))", "_pre_detach('sub0', 'root')", "_post_detach('sub0', 'root')", "_pre_detach('sub0A', 'root')", "_post_detach('sub0A', 'root')", "_post_detach_children('root', ('sub0', 'sub0A'))", "_pre_attach_children('root', ('sub0', 'sub0A'))", "_pre_attach('sub0', 'root')", "_post_attach('sub0', 'root')", "_pre_attach('sub0A', 'root')", "_post_attach('sub0A', 'root')", "_post_attach_children('root', ('sub0', 'sub0A'))", ] TNode.TRACKING.clear() s0.children = [s0a] assert TNode.TRACKING == [ "_pre_detach_children('sub0', ('sub0B',))", "_pre_detach('sub0B', 'sub0')", "_post_detach('sub0B', 'sub0')", "_post_detach_children('sub0', ('sub0B',))", "_pre_attach_children('sub0', ('sub0B',))", "_pre_attach('sub0B', 'sub0')", "_post_attach('sub0B', 'sub0')", "_post_attach_children('sub0', ('sub0B',))", ] TNode.TRACKING.clear() s0a.children = [s1] assert TNode.TRACKING == [ "_pre_detach_children('sub0B', ())", "_post_detach_children('sub0B', ())", "_pre_attach_children('sub0B', ('sub0A',))", "_pre_detach('sub0A', 'root')", "_post_detach('sub0A', 'root')", "_pre_attach('sub0A', 'sub0B')", "_post_attach('sub0A', 'sub0B')", "_post_attach_children('sub0B', ('sub0A',))", ] TNode.TRACKING.clear() root.children = [s0, s1] assert TNode.TRACKING == [ "_pre_detach_children('root', ('sub0',))", "_pre_detach('sub0', 'root')", "_post_detach('sub0', 'root')", "_post_detach_children('root', ('sub0',))", "_pre_attach_children('root', ('sub0', 'sub0A'))", "_pre_attach('sub0', 'root')", "_post_attach('sub0', 'root')", "_pre_detach('sub0A', 'sub0B')", "_post_detach('sub0A', 'sub0B')", "_pre_attach('sub0A', 'root')", "_post_attach('sub0A', 'root')", "_post_attach_children('root', ('sub0', 'sub0A'))", ] TNode.TRACKING.clear() anytree-2.12.1/tests/test_node_integrity.py000066400000000000000000000036321452550712300210520ustar00rootroot00000000000000# -*- coding: utf-8 -*- from anytree import LoopError, Node, NodeMixin, PostOrderIter, PreOrderIter, TreeError from .helper import assert_raises, eq_ def test_readonly_pre(): """Read Only Use case, where Exceptions in _pre_{attach,detach} avoid modifications.""" class ReadonlyError(RuntimeError): pass class ReadonlyNode(Node): _is_readonly = False def _pre_attach(self, parent): if self._is_readonly: raise ReadonlyError() def _pre_detach(self, parent): if self._is_readonly: raise ReadonlyError() # construct and make readonly! root = ReadonlyNode("root") s0 = ReadonlyNode("sub0", parent=root) s0b = ReadonlyNode("sub0B", parent=s0) s0a = ReadonlyNode("sub0A", parent=s0) s1 = ReadonlyNode("sub1", parent=root) s1a = ReadonlyNode("sub1A", parent=s1) s1b = ReadonlyNode("sub1B", parent=s1) s1c = ReadonlyNode("sub1C", parent=s1) s1ca = ReadonlyNode("sub1Ca", parent=s1c) ReadonlyNode._is_readonly = True def check(): eq_(root.parent, None) eq_(root.children, tuple([s0, s1])) eq_(s0.parent, root) eq_(s0.children, tuple([s0b, s0a])) eq_(s0b.parent, s0) eq_(s0b.children, tuple()) eq_(s0a.parent, s0) eq_(s0a.children, tuple()) eq_(s1.parent, root) eq_(s1.children, tuple([s1a, s1b, s1c])) eq_(s1a.parent, s1) eq_(s1a.children, tuple()) eq_(s1b.parent, s1) eq_(s1b.children, tuple()) eq_(s1c.parent, s1) eq_(s1c.children, tuple([s1ca])) eq_(s1ca.parent, s1c) eq_(s1ca.children, tuple()) check() with assert_raises(ReadonlyError, ""): s1ca.parent = s0 check() with assert_raises(ReadonlyError, ""): s1ca.parent = None check() with assert_raises(ReadonlyError, ""): s0.children = [] check() anytree-2.12.1/tests/test_node_sep.py000066400000000000000000000045231452550712300176230ustar00rootroot00000000000000# -*- coding: utf-8 -*- """Test custom node separator.""" import six import anytree as at from .helper import assert_raises, eq_ class MyNode(at.Node): separator = "|" def test_render(): """Render string cast.""" root = MyNode("root") s0 = MyNode("sub0", parent=root) MyNode("sub0B", parent=s0) MyNode("sub0A", parent=s0) MyNode("sub1", parent=root) r = at.RenderTree(root) assert str(r).splitlines() == [ "MyNode('|root')", "├── MyNode('|root|sub0')", "│ ├── MyNode('|root|sub0|sub0B')", "│ └── MyNode('|root|sub0|sub0A')", "└── MyNode('|root|sub1')", ] def test_get(): """Get.""" top = MyNode("top", parent=None) sub0 = MyNode("sub0", parent=top) sub0sub0 = MyNode("sub0sub0", parent=sub0) sub0sub1 = MyNode("sub0sub1", parent=sub0) sub1 = MyNode("sub1", parent=top) r = at.Resolver("name") eq_(r.get(top, "sub0|sub0sub0"), sub0sub0) eq_(r.get(sub1, ".."), top) eq_(r.get(sub1, "..|sub0|sub0sub1"), sub0sub1) eq_(r.get(sub1, "."), sub1) eq_(r.get(sub1, ""), sub1) with assert_raises(at.ChildResolverError, "MyNode('|top') has no child sub2. Children are: 'sub0', 'sub1'."): r.get(top, "sub2") eq_(r.get(sub0sub0, "|top"), top) eq_(r.get(sub0sub0, "|top|sub0"), sub0) with assert_raises(at.ResolverError, "root node missing. root is '|top'."): r.get(sub0sub0, "|") with assert_raises(at.ResolverError, "unknown root node '|bar'. root is '|top'."): r.get(sub0sub0, "|bar") def test_glob(): """Wildcard.""" top = MyNode("top", parent=None) sub0 = MyNode("sub0", parent=top) sub0sub0 = MyNode("sub0", parent=sub0) sub0sub1 = MyNode("sub1", parent=sub0) sub0sub1sub0 = MyNode("sub0", parent=sub0sub1) MyNode("sub1", parent=sub0sub1) sub1 = MyNode("sub1", parent=top) sub1sub0 = MyNode("sub0", parent=sub1) r = at.Resolver() eq_(r.glob(top, "*|*|sub0"), [sub0sub1sub0]) eq_(r.glob(top, "sub0|sub?"), [sub0sub0, sub0sub1]) eq_(r.glob(sub1, "..|.|*"), [sub0, sub1]) eq_(r.glob(top, "*|*"), [sub0sub0, sub0sub1, sub1sub0]) eq_(r.glob(top, "*|sub0"), [sub0sub0, sub1sub0]) with assert_raises(at.ChildResolverError, "MyNode('|top|sub1') has no child sub1. Children are: 'sub0'."): r.glob(top, "sub1|sub1") anytree-2.12.1/tests/test_node_symlink.py000066400000000000000000000031341452550712300205170ustar00rootroot00000000000000# -*- coding: utf-8 -*- from anytree import AnyNode, Node, NodeMixin, PostOrderIter, PreOrderIter, SymlinkNode from .helper import eq_ def test_symlink(): root = Node("root") s0 = Node("sub0", parent=root) s0b = Node("sub0B", parent=s0) s0a = Node("sub0A", parent=s0) s1 = Node("sub1", parent=root, foo=4) s1a = Node("sub1A", parent=s1) s1b = Node("sub1B", parent=s1) s1c = Node("sub1C", parent=s1) s1ca = Node("sub1Ca", parent=s1c) ln = SymlinkNode(s1, parent=root, blub=17) l0 = Node("l0", parent=ln) eq_(root.parent, None) eq_(root.children, tuple([s0, s1, ln])) eq_(s0.parent, root) eq_(s0.children, tuple([s0b, s0a])) eq_(s0b.parent, s0) eq_(s0b.children, tuple()) eq_(s0a.parent, s0) eq_(s0a.children, tuple()) eq_(s1.parent, root) eq_(s1.children, tuple([s1a, s1b, s1c])) eq_(s1.foo, 4) eq_(s1a.parent, s1) eq_(s1a.children, tuple()) eq_(s1b.parent, s1) eq_(s1b.children, tuple()) eq_(s1c.parent, s1) eq_(s1c.children, tuple([s1ca])) eq_(s1ca.parent, s1c) eq_(s1ca.children, tuple()) eq_(ln.parent, root) eq_(ln.children, tuple([l0])) eq_(ln.foo, 4) eq_(s1.blub, 17) eq_(ln.blub, 17) ln.bar = 9 eq_(ln.bar, 9) eq_(s1.bar, 9) result = [node.name for node in PreOrderIter(root)] eq_(result, ["root", "sub0", "sub0B", "sub0A", "sub1", "sub1A", "sub1B", "sub1C", "sub1Ca", "sub1", "l0"]) result = [node.name for node in PostOrderIter(root)] eq_(result, ["sub0B", "sub0A", "sub0", "sub1A", "sub1B", "sub1Ca", "sub1C", "sub1", "l0", "sub1", "root"]) anytree-2.12.1/tests/test_pickle.py000066400000000000000000000015051452550712300172730ustar00rootroot00000000000000# -*- coding: utf-8 -*- import pickle from anytree import AnyNode, LoopError, Node, NodeMixin, PostOrderIter, PreOrderIter, RenderTree, SymlinkNode, TreeError def test_pickle(tmp_path): """Pickling Compatibilty.""" root = Node(name="root") a = Node(name="a", parent=root) b = Node(name="b", parent=a) c = SymlinkNode(target=b, parent=a) lines = str(RenderTree(root)).splitlines() assert lines == [ "Node('/root')", "└── Node('/root/a')", " ├── Node('/root/a/b')", " └── SymlinkNode(Node('/root/a/b'))", ] filepath = tmp_path / "test.pkl" with open(filepath, "wb") as file: pickle.dump(root, file) with open(filepath, "rb") as file: loaded = pickle.load(file) assert str(RenderTree(root)).splitlines() == lines anytree-2.12.1/tests/test_render.py000066400000000000000000000117501452550712300173060ustar00rootroot00000000000000# -*- coding: utf-8 -*- import anytree def test_render_str(): """Render string cast.""" root = anytree.Node("root") s0 = anytree.Node("sub0", parent=root) anytree.Node("sub0B", parent=s0) anytree.Node("sub0A", parent=s0) anytree.Node("sub1", parent=root) r = anytree.RenderTree(root) assert str(r).splitlines() == [ "Node('/root')", "├── Node('/root/sub0')", "│ ├── Node('/root/sub0/sub0B')", "│ └── Node('/root/sub0/sub0A')", "└── Node('/root/sub1')", ] r = anytree.RenderTree(root, childiter=lambda nodes: [n for n in nodes if len(n.name) < 5]) assert str(r).splitlines() == [ "Node('/root')", "├── Node('/root/sub0')", "└── Node('/root/sub1')", ] def test_render_repr(): """Render representation.""" root = anytree.Node("root") anytree.Node("sub", parent=root) r = anytree.RenderTree(root) assert repr(r) == "RenderTree(Node('/root'), style=ContStyle(), " "childiter=)" def test_render(): """Rendering.""" root = anytree.Node("root", lines=["c0fe", "c0de"]) s0 = anytree.Node("sub0", parent=root, lines=["ha", "ba"]) s0b = anytree.Node("sub0B", parent=s0, lines=["1", "2", "3"]) s0a = anytree.Node("sub0A", parent=s0, lines=["a", "b"]) s1 = anytree.Node("sub1", parent=root, lines=["Z"]) r = anytree.RenderTree(root, style=anytree.DoubleStyle) result = [(pre, node) for pre, _, node in r] assert result == [ ("", root), ("╠══ ", s0), ("║ ╠══ ", s0b), ("║ ╚══ ", s0a), ("╚══ ", s1), ] def multi(root): for pre, fill, node in anytree.RenderTree(root): yield "%s%s" % (pre, node.lines[0]), node for line in node.lines[1:]: yield "%s%s" % (fill, line), node assert list(multi(root)) == [ ("c0fe", root), ("c0de", root), ("├── ha", s0), ("│ ba", s0), ("│ ├── 1", s0b), ("│ │ 2", s0b), ("│ │ 3", s0b), ("│ └── a", s0a), ("│ b", s0a), ("└── Z", s1), ] def test_maxlevel(): root = anytree.Node("root", lines=["c0fe", "c0de"]) s0 = anytree.Node("sub0", parent=root, lines=["ha", "ba"]) s0b = anytree.Node("sub0B", parent=s0, lines=["1", "2", "3"]) s0a = anytree.Node("sub0A", parent=s0, lines=["a", "b"]) s1 = anytree.Node("sub1", parent=root, lines=["Z"]) r = anytree.RenderTree(root, maxlevel=2) result = [(pre, node) for pre, _, node in r] assert result == [ ("", root), ("├── ", s0), ("└── ", s1), ] def test_asciistyle(): style = anytree.AsciiStyle() assert style.vertical == "| " assert style.cont == "|-- " assert style.end == "+-- " def test_contstyle(): style = anytree.ContStyle() assert style.vertical == "\u2502 " assert style.cont == "\u251c\u2500\u2500 " assert style.end == "\u2514\u2500\u2500 " def test_controundstyle(): style = anytree.ContRoundStyle() assert style.vertical == "\u2502 " assert style.cont == "\u251c\u2500\u2500 " assert style.end == "\u2570\u2500\u2500 " def test_doublestyle(): style = anytree.DoubleStyle() assert style.vertical == "\u2551 " assert style.cont == "\u2560\u2550\u2550 " assert style.end == "\u255a\u2550\u2550 " def test_by_attr(): """by attr.""" root = anytree.Node("root", lines=["root"]) s0 = anytree.Node("sub0", parent=root, lines=["su", "b0"]) anytree.Node("sub0B", parent=s0, lines=["sub", "0B"]) anytree.Node("sub0A", parent=s0) anytree.Node("sub1", parent=root, lines=["sub1"]) assert anytree.RenderTree(root).by_attr().splitlines() == [ "root", "├── sub0", "│ ├── sub0B", "│ └── sub0A", "└── sub1", ] assert anytree.RenderTree(root).by_attr("lines").splitlines() == [ "root", "├── su", "│ b0", "│ ├── sub", "│ │ 0B", "│ └── ", "└── sub1", ] assert anytree.RenderTree(root).by_attr(lambda node: ":".join(node.name)).splitlines() == [ "r:o:o:t", "├── s:u:b:0", "│ ├── s:u:b:0:B", "│ └── s:u:b:0:A", "└── s:u:b:1", ] def test_repr(): """Repr.""" class ReprNode(anytree.Node): def __repr__(self): return "{name}\n{name}".format(name=self.name) root = ReprNode("root", lines=["root"]) s0 = ReprNode("sub0", parent=root, lines=["su", "b0"]) ReprNode("sub0B", parent=s0, lines=["sub", "0B"]) ReprNode("sub0A", parent=s0) ReprNode("sub1", parent=root, lines=["sub1"]) bystr = str(anytree.RenderTree(root)).splitlines() byident = anytree.RenderTree(root).by_attr(lambda node: node).splitlines() assert bystr == byident anytree-2.12.1/tests/test_resolver.py000066400000000000000000000227701452550712300176740ustar00rootroot00000000000000# -*- coding: utf-8 -*- from enum import IntEnum from pytest import raises import anytree as at from anytree import Node, Resolver from .helper import assert_raises def test_get(): """Get.""" top = at.Node("top", parent=None) sub0 = at.Node("sub0", parent=top) sub0sub0 = at.Node("sub0sub0", parent=sub0) sub0sub1 = at.Node("sub0sub1", parent=sub0) sub1 = at.Node("sub1", parent=top) r = at.Resolver("name") assert r.get(top, "sub0/sub0sub0") == sub0sub0 assert r.get(sub1, "..") == top assert r.get(sub1, "../") == top assert r.get(sub1, "../.") == top assert r.get(sub1, "../sub0/sub0sub1") == sub0sub1 assert r.get(sub1, ".") == sub1 assert r.get(sub1, "") == sub1 with assert_raises(at.ChildResolverError, "Node('/top') has no child sub2. Children are: 'sub0', 'sub1'."): r.get(top, "sub2") assert r.get(sub0sub0, "/top") == top assert r.get(sub0sub0, "/top/sub0") == sub0 with assert_raises(at.RootResolverError, "Cannot go above root node Node('/top')"): r.get(top, "..") with assert_raises(at.ResolverError, "root node missing. root is '/top'."): r.get(sub0sub0, "/") with assert_raises(at.ResolverError, "unknown root node '/bar'. root is '/top'."): r.get(sub0sub0, "/bar") def test_get_relaxed(): """Get in relaxed mode.""" top = at.Node("top", parent=None) sub0 = at.Node("sub0", parent=top) sub0sub0 = at.Node("sub0sub0", parent=sub0) sub0sub1 = at.Node("sub0sub1", parent=sub0) sub1 = at.Node("sub1", parent=top) r = at.Resolver("name", relax=True) assert r.get(top, "sub0/sub0sub0") == sub0sub0 assert r.get(sub1, "..") == top assert r.get(sub1, "../") == top assert r.get(sub1, "../.") == top assert r.get(sub1, "../sub0/sub0sub1") == sub0sub1 assert r.get(sub1, ".") == sub1 assert r.get(sub1, "") == sub1 assert r.get(top, "sub2") is None assert r.get(sub0sub0, "/top") == top assert r.get(sub0sub0, "/top/sub0") == sub0 assert r.get(top, "..") is None assert r.get(sub0sub0, "/") is None assert r.get(sub0sub0, "/bar") is None def test_glob(): """Wildcard.""" top = at.Node("top", parent=None) sub0 = at.Node("sub0", parent=top) sub0sub0 = at.Node("sub0", parent=sub0) sub0sub1 = at.Node("sub1", parent=sub0) sub0sub1sub0 = at.Node("sub0", parent=sub0sub1) at.Node("sub1", parent=sub0sub1) sub1 = at.Node("sub1", parent=top) sub1sub0 = at.Node("sub0", parent=sub1) r = at.Resolver() assert r.glob(top, "sub0/sub0") == [sub0sub0] assert r.glob(sub1, "..") == [top] assert r.glob(sub1, "../") == [top] assert r.glob(sub1, "../.") == [top] assert r.glob(sub1, "../././.") == [top] assert r.glob(sub1, ".././././sub0/..") == [top] assert r.glob(sub1, "../sub0/sub1") == [sub0sub1] assert r.glob(sub1, ".") == [sub1] assert r.glob(sub1, "./") == [sub1] assert r.glob(sub1, "") == [sub1] assert r.glob(sub1, "/top") == [top] assert r.glob(sub1, "/*") == [top] assert r.glob(top, "*/*/sub0") == [sub0sub1sub0] assert r.glob(top, "sub0/sub?") == [sub0sub0, sub0sub1] assert r.glob(sub1, ".././*") == [sub0, sub1] assert r.glob(top, "*/*") == [sub0sub0, sub0sub1, sub1sub0] assert r.glob(top, "*/sub0") == [sub0sub0, sub1sub0] with assert_raises(at.RootResolverError, "Cannot go above root node Node('/top')"): r.glob(top, "..") with assert_raises(at.RootResolverError, "Cannot go above root node Node('/top')"): r.glob(sub1, ".././..") with assert_raises(at.ChildResolverError, "Node('/top/sub1') has no child sub1. Children are: 'sub0'."): r.glob(top, "sub1/sub1") with assert_raises(at.ResolverError, "unknown root node '/z*'. root is '/top'."): r.glob(sub1, "/z*") # Recursive matching assert r.glob(top, "**/sub0") == [sub0, sub0sub0, sub0sub1sub0, sub1sub0] assert r.glob(top, "**/sub0/sub0") == [sub0sub0] assert r.glob(top, "**/**/sub0") == [sub0, sub0sub0, sub0sub1sub0, sub1sub0] assert r.glob(top, "sub0/**/sub0") == [sub0sub0, sub0sub1sub0] with assert_raises(at.ResolverError, "unknown root node '/sub0'. root is '/top'."): r.glob(top, "/sub0/**/sub0") def test_glob_relaxed(): """Wildcard relaxed.""" top = at.Node("top", parent=None) sub0 = at.Node("sub0", parent=top) sub0sub0 = at.Node("sub0", parent=sub0) sub0sub1 = at.Node("sub1", parent=sub0) sub0sub1sub0 = at.Node("sub0", parent=sub0sub1) at.Node("sub1", parent=sub0sub1) sub1 = at.Node("sub1", parent=top) sub1sub0 = at.Node("sub0", parent=sub1) r = at.Resolver(relax=True) assert r.glob(top, "sub0/sub0") == [sub0sub0] assert r.glob(sub1, "..") == [top] assert r.glob(sub1, "../") == [top] assert r.glob(sub1, "../.") == [top] assert r.glob(sub1, "../././.") == [top] assert r.glob(sub1, ".././././sub0/..") == [top] assert r.glob(sub1, "../sub0/sub1") == [sub0sub1] assert r.glob(sub1, ".") == [sub1] assert r.glob(sub1, "./") == [sub1] assert r.glob(sub1, "") == [sub1] assert r.glob(sub1, "/top") == [top] assert r.glob(sub1, "/*") == [top] assert r.glob(top, "*/*/sub0") == [sub0sub1sub0] assert r.glob(top, "sub0/sub?") == [sub0sub0, sub0sub1] assert r.glob(sub1, ".././*") == [sub0, sub1] assert r.glob(top, "*/*") == [sub0sub0, sub0sub1, sub1sub0] assert r.glob(top, "*/sub0") == [sub0sub0, sub1sub0] assert r.glob(top, "..") == [] assert r.glob(sub1, ".././..") == [] assert r.glob(top, "sub1/sub1") == [] assert r.glob(sub1, "/z*") == [] # Recursive matching assert r.glob(top, "**/sub0") == [sub0, sub0sub0, sub0sub1sub0, sub1sub0] assert r.glob(top, "**/sub0/sub0") == [sub0sub0] assert r.glob(top, "**/**/sub0") == [sub0, sub0sub0, sub0sub1sub0, sub1sub0] assert r.glob(top, "sub0/**/sub0") == [sub0sub0, sub0sub1sub0] assert r.glob(top, "/sub0/**/sub0") == [] def test_glob_cache(): """Wildcard Cache.""" root = at.Node("root") sub0 = at.Node("sub0", parent=root) sub1 = at.Node("sub1", parent=root) r = at.Resolver() # strip down cache size at.resolver._MAXCACHE = 2 at.Resolver._match_cache.clear() assert len(at.Resolver._match_cache) == 0 assert r.glob(root, "sub0") == [sub0] assert len(at.Resolver._match_cache) == 1 assert r.glob(root, "sub1") == [sub1] assert len(at.Resolver._match_cache) == 2 assert r.glob(root, "sub*") == [sub0, sub1] assert len(at.Resolver._match_cache) == 1 def test_same_name(): """Same Name.""" root = at.Node("root") sub0 = at.Node("sub", parent=root) sub1 = at.Node("sub", parent=root) r = at.Resolver() assert r.get(root, "sub") == sub0 assert r.glob(root, "sub") == [sub0, sub1] def test_ignorecase(): """Case insensitive resolver""" root = at.Node("root") sub0 = at.Node("sUB0", parent=root) sub1 = at.Node("sub1", parent=root) r = at.Resolver(ignorecase=True) assert r.get(root, "SUB0") == sub0 assert r.get(root, "sub0") == sub0 assert r.get(root, "sUB0") == sub0 assert r.glob(root, "SU*1") == [sub1] assert r.glob(root, "/*/SU*1") == [sub1] assert r.glob(sub0, "../*1") == [sub1] def test_enum(): class Animals(IntEnum): Mammal = 1 Cat = 2 Dog = 3 root = Node("ANIMAL") mammal = Node(Animals.Mammal, parent=root) cat = Node(Animals.Cat, parent=mammal) dog = Node(Animals.Dog, parent=mammal) r = Resolver() assert r.glob(root, "/ANIMAL/*") == [mammal] assert r.glob(root, "/ANIMAL/*/*") == [cat, dog] def test_glob_consistency(): """Ensure Consistency""" node = at.Node("root") resolver = at.Resolver() assert resolver.glob(node, "/root") == [node] assert resolver.get(node, "/root") == node assert resolver.glob(node, ".") == [node] assert resolver.get(node, ".") == node assert resolver.glob(node, "") == [node] assert resolver.get(node, "") == node with raises(at.ResolverError): assert resolver.glob(node, "/") with raises(at.ResolverError): assert resolver.get(node, "/") with raises(at.RootResolverError): assert resolver.glob(node, "..") with raises(at.RootResolverError): assert resolver.get(node, "..") with raises(at.ResolverError): assert resolver.glob(node, "root") with raises(at.ResolverError): assert resolver.get(node, "root") with raises(at.ResolverError): assert resolver.glob(node, "bla") with raises(at.ResolverError): assert resolver.get(node, "bla") with raises(at.ResolverError): assert resolver.glob(node, "/bla") with raises(at.ResolverError): assert resolver.get(node, "/bla") def test_glob_consistency_relax(): """Ensure Consistency on relaxed.""" node = at.Node("root") resolver = at.Resolver(relax=True) assert resolver.glob(node, "/root") == [node] assert resolver.get(node, "/root") == node assert resolver.glob(node, ".") == [node] assert resolver.get(node, ".") == node assert resolver.glob(node, "") == [node] assert resolver.get(node, "") == node assert resolver.glob(node, "/") == [] assert resolver.get(node, "/") is None assert resolver.glob(node, "..") == [] assert resolver.get(node, "..") is None assert resolver.glob(node, "root") == [] assert resolver.get(node, "root") is None assert resolver.glob(node, "bla") == [] assert resolver.get(node, "bla") is None assert resolver.glob(node, "/bla") == [] assert resolver.get(node, "/bla") is None anytree-2.12.1/tests/test_search.py000066400000000000000000000052001452550712300172650ustar00rootroot00000000000000from enum import IntEnum from anytree import AsciiStyle, CountError, Node, PreOrderIter, RenderTree, find, find_by_attr, findall, findall_by_attr from .helper import assert_raises, eq_ def test_findall(): f = Node("f") b = Node("b", parent=f) a = Node("a", parent=b) d = Node("d", parent=b) c = Node("c", parent=d) e = Node("e", parent=d) eq_(findall(f, filter_=lambda node: node.name in ("a", "b")), (b, a)) eq_(findall(f, filter_=lambda node: d in node.path), (d, c, e)) with assert_raises( CountError, ("Expecting at least 4 elements, but found 3. " "(Node('/f/b/d'), Node('/f/b/d/c'), Node('/f/b/d/e'))"), ): findall(f, filter_=lambda node: d in node.path, mincount=4) with assert_raises( CountError, ("Expecting 2 elements at maximum, but found 3. " "(Node('/f/b/d'), Node('/f/b/d/c'), Node('/f/b/d/e'))"), ): findall(f, filter_=lambda node: d in node.path, maxcount=2) def test_findall_by_attr(): f = Node("f") b = Node("b", parent=f) Node("a", parent=b) d = Node("d", parent=b) Node("c", parent=d) Node("e", parent=d) eq_(findall_by_attr(f, "d"), (d,)) with assert_raises(CountError, ("Expecting at least 1 elements, but found 0.")): findall_by_attr(f, "z", mincount=1) def test_find(): f = Node("f") b = Node("b", parent=f) Node("a", parent=b) d = Node("d", parent=b) Node("c", parent=d) Node("e", parent=d) g = Node("g", parent=f) i = Node("i", parent=g) Node("h", parent=i) eq_(find(f, lambda n: n.name == "d"), d) eq_(find(f, lambda n: n.name == "z"), None) with assert_raises( CountError, ( "Expecting 1 elements at maximum, but found 5. " "(Node('/f/b'), Node('/f/b/a'), Node('/f/b/d'), Node('/f/b/d/c'), Node('/f/b/d/e'))" ), ): find(f, lambda n: b in n.path) def test_find_by_attr(): f = Node("f") b = Node("b", parent=f) Node("a", parent=b) d = Node("d", parent=b) c = Node("c", parent=d, foo=4) Node("e", parent=d) g = Node("g", parent=f) i = Node("i", parent=g) Node("h", parent=i) eq_(find_by_attr(f, "d"), d) eq_(find_by_attr(f, name="foo", value=4), c) eq_(find_by_attr(f, name="foo", value=8), None) def test_enum(): class Animals(IntEnum): Mammal = 1 Cat = 2 Dog = 3 root = Node("ANIMAL") mammal = Node(Animals.Mammal, parent=root) cat = Node(Animals.Cat, parent=mammal) dog = Node(Animals.Dog, parent=mammal) eq_(findall(root), (root, mammal, cat, dog)) eq_(findall_by_attr(root, Animals.Cat), (cat,)) anytree-2.12.1/tests/test_special_methods_access.py000066400000000000000000000227571452550712300225240ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ The methods of the `NodeMixin` class should not access the user class special methods. For instance, the user define a `MyNode` class as bellow: ```python from anytree import NodeMixin class MyNode(NodeMixin): def __init__(self, name, parent=None, children=None): super(MyNode, self).__init__() self.name = name self.parent = parent if children: self.children = children ``` In this class, the used can implement some special methods, like ``__eq__`` or ``__len__``, which can have a specific meaning not related to the Tree structure. A good exemple could be a `NodeMixin` subclass which also implements `collections.abc.Mapping`: ```python import collections from anytree import NodeMixin class MyMapping(NodeMixin, collections.abc.Mapping): def __init__(self, name, parent=None, children=None): super(MyMapping, self).__init__() self.name = name self.parent = parent if children: self.children = children def __iter__(self): for child in self.children: yield child for item in child: yield item def __len__(self): return len(list(iter(self))) def __getitem__(self, name): for child in self: if child.name == name: return child raise KeyError(name) ``` In this example, the `NodeMixin` class shouldn't make any call to `__iter__`, `__len__` or `__getitem__`. To avoid that, the `NodeMixin` class should respect the following rules: - Do not compare nodes by value but by reference. In other words, the `NodeMixin` class should not use `==` or `!=` but compare nodes with the `id()` function. Comparison could be done with the `is` or `is not` operator. Avoid using `in` or `not in`. - Do not use truth value testing to check if a node is "empty". The `NodeMixin` class should not `if node`, `if not node`, `while node` nor `while not node`. Instead, it must compare the node with `None` like this: `if node is not None`, `if node is None`, `while node is not None` or `while node is None`. - Do not presume that nodes are hashable. The `NodeMixin` class should not store nodes in `set` or `dict`. Instead, it can store node IDs using the `id()` function. """ import functools import unittest from anytree import NodeMixin try: from collections.abc import Mapping except ImportError: from collections import Mapping # List of method names to which we want to control access: # # - This list does not contain the access control methods (`__getattribute__`, `__getattr__`, # `__setattr__`, and `__delattr__`) which are used by the NodeMixin class anyway. # - This list does not contain `__new__`, `__init__`, and `__del__` which are required for testing. # - Some methods are not available in all Python version: `__bytes__`, `__unicode__` and `__dir__`. SPECIAL_METHODS = [ # rich comparison methods "__lt__", "__le__", "__eq__", "__ne__", "__gt__", "__ge__", # repr/str/bytes "__repr__", "__str__", # "__bytes__", # "__unicode__", "__format__", # hashing "__hash__", # attribute listing # "__dir__", # pickle protocol "__getnewargs_ex__", "__getnewargs__", "__getstate__", "__setstate__", "__reduce_ex__", # object size "__sizeof__", # truth value testing "__bool__", # callable objects "__call__", # container types "__len__", "__length_hint__", "__getitem__", "__setitem__", "__delitem__", "__missing__", "__iter__", "__reversed__", "__contains__", # numeric types "__add__", "__sub__", "__mul__", "__matmul__", "__truediv__", "__floordiv__", "__mod__", "__divmod__", "__pow__", "__lshift__", "__rshift__", "__and__", "__xor__", "__or__", "__radd__", "__rsub__", "__rmul__", "__rmatmul__", "__rtruediv__", "__rfloordiv__", "__rmod__", "__rdivmod__", "__rpow__", "__rlshift__", "__rrshift__", "__rand__", "__rxor__", "__ror__", "__iadd__", "__isub__", "__imul__", "__imatmul__", "__itruediv__", "__ifloordiv__", "__imod__", "__ipow__", "__ilshift__", "__irshift__", "__iand__", "__ixor__", "__ior__", "__neg__", "__pos__", "__abs__", "__invert__", "__complex__", "__int__", "__float__", "__index__", "__round__", "__trunc__", "__floor__", "__ceil__", # With Statement Context Managers "__enter__", "__exit__", ] SPECIAL_METHODS += [attr for attr in ["__bytes__", "__unicode__", "__dir"] if hasattr(object, attr)] def prevent_access(attr, *args, **kwargs): raise AssertionError("invalid call to " + attr) class MyNode(NodeMixin): @staticmethod def __new__(cls, *args, **kwargs): for attr in SPECIAL_METHODS: setattr(cls, attr, functools.partial(prevent_access, attr)) instance = super(NodeMixin, cls).__new__(cls) return instance def __init__(self, name, parent=None, children=None): super(MyNode, self).__init__() self.name = name self.parent = parent if children: self.children = children class TestConsistency(unittest.TestCase): """Control the access to special methods""" def setUp(self): super(TestConsistency, self).setUp() self.root1 = MyNode("root1") self.child1 = MyNode("child1", parent=self.root1) self.child2a = MyNode("child2a", parent=self.child1) self.child2b = MyNode("child2b", parent=self.child1) self.other = MyNode("other") def test_parent__root1(self): _ = self.root1.parent def test_parent__setter__root1(self): self.root1.parent = self.other def test_children__root1(self): _ = self.root1.children def test_children__setter__root1(self): self.root1.children = [self.other] def test_path__root1(self): _ = self.root1.path def test_iter_path_reverse__root1(self): for _ in self.root1.iter_path_reverse(): pass def test_ancestors__root1(self): _ = self.root1.ancestors def test_descendants__root1(self): _ = self.root1.descendants def test_root__root1(self): _ = self.root1.root def test_siblings__root1(self): _ = self.root1.siblings def test_leaves__root1(self): _ = self.root1.leaves def test_is_leaf__root1(self): _ = self.root1.is_leaf def test_is_root__root1(self): _ = self.root1.is_root def test_height__root1(self): _ = self.root1.height def test_depth__root1(self): _ = self.root1.depth def test_parent__child2b(self): _ = self.child2b.parent def test_parent__setter__child2b(self): self.child2b.parent = self.other def test_children__child2b(self): _ = self.child2b.children def test_children__setter__child2b(self): self.child2b.children = [self.other] def test_path__child2b(self): _ = self.child2b.path def test_iter_path_reverse__child2b(self): for _ in self.child2b.iter_path_reverse(): pass def test_ancestors__child2b(self): _ = self.child2b.ancestors def test_descendants__child2b(self): _ = self.child2b.descendants def test_root__child2b(self): _ = self.child2b.root def test_siblings__child2b(self): _ = self.child2b.siblings def test_leaves__child2b(self): _ = self.child2b.leaves def test_is_leaf__child2b(self): _ = self.child2b.is_leaf def test_is_root__child2b(self): _ = self.child2b.is_root def test_height__child2b(self): _ = self.child2b.height def test_depth__child2b(self): _ = self.child2b.depth class MyMapping(NodeMixin, Mapping): """ This class is used to demonstrate a possible implementation which defines some special methods. """ def __init__(self, name, parent=None, children=None): super(MyMapping, self).__init__() self.name = name self.parent = parent if children: self.children = children def __iter__(self): """Iterate over all children recursively.""" for child in self.children: yield child for item in child: yield item def __len__(self): """Total number of children.""" return len(list(iter(self))) def __getitem__(self, name): for child in self: if child.name == name: return child raise KeyError(name) class TestMyMapping(unittest.TestCase): def setUp(self): super(TestMyMapping, self).setUp() self.root1 = MyMapping("root1") self.child1 = MyMapping("child1", parent=self.root1) self.child2a = MyMapping("child2a", parent=self.child1) self.child2b = MyMapping("child2b", parent=self.child1) def test_iter(self): expected_list = [self.child1, self.child2a, self.child2b] for actual, expected in zip(iter(self.root1), expected_list): self.assertIs(actual, expected) def test_len(self): self.assertEqual(len(self.root1), 3) def test_getitem(self): self.assertIs(self.root1["child1"], self.child1) self.assertIs(self.root1["child2a"], self.child2a) self.assertIs(self.root1["child2b"], self.child2b) with self.assertRaises(KeyError): _ = self.root1["missing"] anytree-2.12.1/tests/test_uniquedotexporter.py000066400000000000000000000044221452550712300216330ustar00rootroot00000000000000# -*- coding: utf-8 -*- import pathlib from pytest import fixture from anytree import Node from anytree.exporter import UniqueDotExporter REFDATA = pathlib.Path(__file__).parent / "refdata" / "test_uniquedotexporter" from .util import assert_gen @fixture def root(): root = Node("root") s0 = Node("sub0", parent=root, edge=2) Node("sub0B", parent=s0, foo=4, edge=109) Node("sub0A", parent=s0, edge="") s1 = Node("sub1", parent=root, edge="") Node("sub1A", parent=s1, edge=7) Node('sub1"B', parent=s1, edge=8) s1c = Node("su\\b1C", parent=s1, edge=22) Node("sub1Ca", parent=s1c, edge=42) yield root def test_tree(tmp_path, root): """Tree.""" UniqueDotExporter(root).to_dotfile(tmp_path / "tree.dot") assert_gen(tmp_path, REFDATA / "tree") def test_tree_custom(tmp_path, root): """Tree Custom.""" def nodenamefunc(node): return "%s:%s" % (node.name, node.depth) def edgeattrfunc(node, child): return 'label="%s:%s"' % (node.name, child.name) def nodefunc(node): return '("%s")' % (node.name) def edgefunc(node, child): return f"--{child.edge}-->" UniqueDotExporter( root, options=["rankdir=LR;"], nodenamefunc=nodenamefunc, nodeattrfunc=lambda node: "shape=box", edgeattrfunc=edgeattrfunc, ).to_dotfile(tmp_path / "tree_custom.dot") assert_gen(tmp_path, REFDATA / "tree_custom") def test_tree_filter(tmp_path, root): """Tree with Filter.""" UniqueDotExporter(root, filter_=lambda node: node.name.startswith("sub")).to_dotfile(tmp_path / "tree_filter.dot") assert_gen(tmp_path, REFDATA / "tree_filter") def test_tree_stop(tmp_path, root): """Tree with stop.""" UniqueDotExporter(root, stop=lambda node: node.name == "sub1").to_dotfile(tmp_path / "tree_stop.dot") assert_gen(tmp_path, REFDATA / "tree_stop") def test_tree_maxlevel(tmp_path, root): """Tree with maxlevel.""" UniqueDotExporter(root, maxlevel=2).to_dotfile(tmp_path / "tree_maxlevel.dot") assert_gen(tmp_path, REFDATA / "tree_maxlevel") def test_esc(): """Test proper escape of quotes.""" n = Node(r'6"-6\"') assert tuple(UniqueDotExporter(n)) == ( "digraph tree {", ' "0x0" [label="6"-6\\""];', "}", ) anytree-2.12.1/tests/test_util.py000066400000000000000000000022711452550712300170020ustar00rootroot00000000000000# -*- coding: utf-8 -*- from anytree import Node from anytree.util import commonancestors, leftsibling, rightsibling from .helper import eq_ def test_commonancestors(): """commonancestors.""" udo = Node("Udo") marc = Node("Marc", parent=udo) lian = Node("Lian", parent=marc) dan = Node("Dan", parent=udo) jet = Node("Jet", parent=dan) joe = Node("Joe", parent=dan) eq_(commonancestors(jet, joe), (udo, dan)) eq_(commonancestors(jet, marc), (udo,)) eq_(commonancestors(jet), (udo, dan)) eq_(commonancestors(), ()) eq_(commonancestors(jet, lian), (udo,)) def test_leftsibling(): """leftsibling.""" dan = Node("Dan") jet = Node("Jet", parent=dan) jan = Node("Jan", parent=dan) joe = Node("Joe", parent=dan) eq_(leftsibling(dan), None) eq_(leftsibling(jet), None) eq_(leftsibling(jan), jet) eq_(leftsibling(joe), jan) def test_rightsibling(): """rightsibling.""" dan = Node("Dan") jet = Node("Jet", parent=dan) jan = Node("Jan", parent=dan) joe = Node("Joe", parent=dan) eq_(rightsibling(dan), None) eq_(rightsibling(jet), jan) eq_(rightsibling(jan), joe) eq_(rightsibling(joe), None) anytree-2.12.1/tests/test_walker.py000066400000000000000000000013601452550712300173100ustar00rootroot00000000000000from anytree import Node, Walker, WalkError from .helper import assert_raises, eq_ def test_walker(): """walk test.""" f = Node("f") b = Node("b", parent=f) a = Node("a", parent=b) d = Node("d", parent=b) c = Node("c", parent=d) e = Node("e", parent=d) g = Node("g", parent=f) i = Node("i", parent=g) h = Node("h", parent=i) w = Walker() eq_(w.walk(f, f), ((), f, ())) eq_(w.walk(f, b), ((), f, (b,))) eq_(w.walk(b, f), ((b,), f, ())) eq_(w.walk(a, f), ((a, b), f, ())) eq_(w.walk(h, e), ((h, i, g), f, (b, d, e))) eq_(w.walk(d, e), ((), d, (e,))) with assert_raises(WalkError, "Node('/a') and Node('/b') are not part of the same tree."): w.walk(Node("a"), Node("b")) anytree-2.12.1/tests/util.py000066400000000000000000000017201452550712300157410ustar00rootroot00000000000000import filecmp import pathlib import shutil LEARN = False def assert_gen(genpath, refpath): """Compare Generated Files Versus Reference.""" genpath.mkdir(parents=True, exist_ok=True) refpath.mkdir(parents=True, exist_ok=True) if LEARN: # pragma: no cover shutil.rmtree(refpath, ignore_errors=True) shutil.copytree(genpath, refpath) gens = [path for path in sorted(genpath.glob("**/*")) if path.is_file() and not "__pycache__" in path.parts] refs = [path for path in sorted(refpath.glob("**/*")) if path.is_file() and not "__pycache__" in path.parts] genfiles = [path.relative_to(genpath) for path in gens] reffiles = [path.relative_to(refpath) for path in refs] assert reffiles == genfiles, f"{reffiles} != {genfiles}" for gen, ref in zip(gens, refs): reftext = ref.read_text(encoding="utf-8") gentext = gen.read_text(encoding="utf-8") assert reftext == gentext, f"{reftext} != {gentext}"