diff --git a/.github/workflows/sphinx.yml b/.github/workflows/sphinx.yml new file mode 100644 index 0000000..978038c --- /dev/null +++ b/.github/workflows/sphinx.yml @@ -0,0 +1,25 @@ +name: "Sphinx: Render docs" + +on: push + +jobs: + build: + runs-on: ubuntu-latest + permissions: + contents: write + id-token: write + steps: + - uses: actions/checkout@v4 + - name: Build HTML + uses: ammaraskar/sphinx-action@dev + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: html-docs + path: docs/build/html/ + - name: Deploy + uses: peaceiris/actions-gh-pages@v3 + if: github.ref == 'refs/heads/main' + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: docs/build/html diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..d0c3cbf --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,20 @@ +# 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 = source +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) diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 0000000..e4b1afb --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1,4 @@ +sphinx-rtd-theme>=3.0.2 +myst-parser>=3.0.1 +myst>=1.0.4 +sphinxcontrib-mermaid>=1.0.0 \ No newline at end of file diff --git a/CHANGES.md b/docs/source/CHANGES.md similarity index 98% rename from CHANGES.md rename to docs/source/CHANGES.md index 28b52e1..50088a7 100644 --- a/CHANGES.md +++ b/docs/source/CHANGES.md @@ -1,3 +1,5 @@ +# Changelog + ## Version 0.1.1 * Make it possible to pass options like `file_format` to `to_image`. diff --git a/docs/source/api.rst b/docs/source/api.rst new file mode 100644 index 0000000..af8fcc0 --- /dev/null +++ b/docs/source/api.rst @@ -0,0 +1,45 @@ +API +================== + +Abstract base classes +--------------------- + +.. automodule:: abstracttree.tree + :members: + :show-inheritance: + +.. automodule:: abstracttree.binarytree + :members: + :show-inheritance: + +Adapters +------------------ + +.. automodule:: abstracttree.adapters + :members: astree, convert_tree + :show-inheritance: + +Export +------------------ + +.. automodule:: abstracttree.export + :members: + :show-inheritance: + +Predicates +------------------ +.. automodule:: abstracttree.predicates + :members: + :show-inheritance: + +HeapTree +------------------ +.. automodule:: abstracttree.heaptree + :members: HeapTree + :show-inheritance: + +Route +------------------ +.. automodule:: abstracttree.route + :members: Route + :show-inheritance: diff --git a/docs/source/conf.py b/docs/source/conf.py new file mode 100644 index 0000000..f02d496 --- /dev/null +++ b/docs/source/conf.py @@ -0,0 +1,46 @@ +# 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 = 'AbstractTree' +copyright = '2024, Laurent Verweijen' +author = 'Laurent Verweijen' +release = 'stable' + +# -- General configuration --------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration + +extensions = [ + 'sphinx.ext.autodoc', + 'sphinx.ext.autosummary', + 'sphinx.ext.githubpages', + 'sphinx.ext.viewcode', + 'myst_parser', + 'sphinxcontrib.mermaid' +] + +templates_path = ['_templates'] +exclude_patterns = [] + + + +# -- Options for HTML output ------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output + +html_theme = 'sphinx_rtd_theme' +html_static_path = ['_static'] + + +myst_enable_extensions = ["colon_fence"] + +# Code is in src. Make sure sphinx can find it +import sys +from pathlib import Path +src_folder = Path(__file__).parent.parent.parent / "src" +print(src_folder) +sys.path.insert(0, str(src_folder.resolve(strict=True))) diff --git a/docs/source/images/latex_img.png b/docs/source/images/latex_img.png new file mode 100644 index 0000000..134625b Binary files /dev/null and b/docs/source/images/latex_img.png differ diff --git a/docs/source/images/str_mermaid.png b/docs/source/images/str_mermaid.png new file mode 100644 index 0000000..ccccaf5 Binary files /dev/null and b/docs/source/images/str_mermaid.png differ diff --git a/docs/source/images/tree_calc_plot.png b/docs/source/images/tree_calc_plot.png new file mode 100644 index 0000000..eaaab7e Binary files /dev/null and b/docs/source/images/tree_calc_plot.png differ diff --git a/docs/source/images/tree_dot.png b/docs/source/images/tree_dot.png new file mode 100644 index 0000000..515b0ac Binary files /dev/null and b/docs/source/images/tree_dot.png differ diff --git a/docs/source/img_1.png b/docs/source/img_1.png new file mode 100644 index 0000000..4c97e90 Binary files /dev/null and b/docs/source/img_1.png differ diff --git a/docs/source/img_2.png b/docs/source/img_2.png new file mode 100644 index 0000000..4c97e90 Binary files /dev/null and b/docs/source/img_2.png differ diff --git a/docs/source/index.rst b/docs/source/index.rst new file mode 100644 index 0000000..779f5d7 --- /dev/null +++ b/docs/source/index.rst @@ -0,0 +1,29 @@ +.. AbstractTree documentation master file, created by + sphinx-quickstart on Mon Feb 19 18:11:55 2024. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to AbstractTree's documentation! +======================================== + +Trees are very common data structure that represents a hierarchy of common nodes. +This package defines abstract base classes for these data structure in order to make code reusable. +It also provides an ``astree`` adapter in case it's not possible to inherit from any of these classes. +Finally, it provides many exports that even work on objects that don't inherit from any of the abstract base classes. + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + + installation + usage_bundled + api + CHANGES + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/docs/source/installation.rst b/docs/source/installation.rst new file mode 100644 index 0000000..47c29a3 --- /dev/null +++ b/docs/source/installation.rst @@ -0,0 +1,17 @@ +Installation +======================================== + +Use `pip `_ to install littletree:: + + $ pip install --upgrade littletree + +In addition you may want to install the following for some export functions:: + + $ pip install --upgrade matplotlib + $ pip install --upgrade pillow + +For some export functions it might help to install: + +- `Graphviz `_ +- `Mermaid `_ +- `LaTeX `_ diff --git a/docs/source/usage_bundled.rst b/docs/source/usage_bundled.rst new file mode 100644 index 0000000..f5dbc75 --- /dev/null +++ b/docs/source/usage_bundled.rst @@ -0,0 +1,239 @@ +Usage +================== + +Abstract tree classes +--------------------- + +.. mermaid:: + + graph TD; + Tree[Tree]; + MutableTree[MutableTree]; + DownTree[DownTree]; + Tree[Tree]; + MutableTree[MutableTree]; + MutableDownTree[MutableDownTree]; + MutableTree[MutableTree]; + BinaryDownTree[BinaryDownTree]; + BinaryTree[BinaryTree]; + Tree-->MutableTree; + DownTree-->Tree; + DownTree-->MutableDownTree; + MutableDownTree-->MutableTree; + DownTree-->BinaryDownTree; + BinaryDownTree-->BinaryTree; + Tree-->BinaryTree; + + +A Downtrees is an object that has links to its direct children. +A Tree is has links to both its children and its parent. +A binary tree has exactly two children (left and right). +A mutable tree can change its structure once created. + ++---------------------+-------------------------------+-------------------------------------+------------------------------------------------------------------------------------+ +| ABC | Inherits from | Abstract Methods | Mixin Methods | ++=====================+===============================+=====================================+====================================================================================+ +| ``AbstractTree`` | | | ``nid``, ``eqv()`` | ++---------------------+-------------------------------+-------------------------------------+------------------------------------------------------------------------------------+ +| ``DownTree`` | ``AbstractTree`` | ``children`` | ``nodes``, ``descendants``, ``leaves``, ``levels``, ``is_leaf``, ``transform()`` | ++---------------------+-------------------------------+-------------------------------------+------------------------------------------------------------------------------------+ +| ``Tree`` | ``DownTree`` | | ``siblings`` | ++---------------------+-------------------------------+-------------------------------------+------------------------------------------------------------------------------------+ +| ``MutableDownTree`` | ``DownTree`` | ``add_child()``, ``remove_child()`` | ``add_children()`` | ++---------------------+-------------------------------+-------------------------------------+------------------------------------------------------------------------------------+ +| ``MutableTree`` | ``Tree``, ``MutableDownTree`` | | ``detach()`` | ++---------------------+-------------------------------+-------------------------------------+------------------------------------------------------------------------------------+ +| ``BinaryDownTree`` | ``DownTree`` | ``left_child``, ``right_child`` | ``children`` | ++---------------------+-------------------------------+-------------------------------------+------------------------------------------------------------------------------------+ +| ``BinaryTree`` | ``BinaryDownTree``, ``Tree`` | | | ++---------------------+-------------------------------+-------------------------------------+------------------------------------------------------------------------------------+ + +In your own code, you can inherit from these trees. +For example, if your tree only has links to children:: + + import abstracttree + from abstracttree import print_tree + + class MyTree(abstracttree.DownTree): + def __init__(self, value, children=()): + self.value = value + self._children = children + + def __str__(self): + return "MyTree " + str(self.value) + + @property + def children(self): + return self._children + + +You can now use this class in the following way to generate output:: + + tree = MyTree(1, children=[MyTree(2), MyTree(3)]) + print_tree(tree) + + # MyTree 1 + # ├─ MyTree 2 + # └─ MyTree 3 + +Adapter +------------------ + +In practice, not all existing tree data structures implement one of the abstract classes specified in `Abstract classes `_. +As a bridge, you can use ``Tree.convert`` to convert these trees to a ``Tree`` instance. +However, whenever possible, it's recommended to inherit from ``Tree`` directly for minimal overhead. + +``Tree.convert`` already does the right thing on many objects of the standard library:: + + # Inheritance hierarchy + Tree.convert(int) + + # Abstract syntax tree + Tree.convert(ast.parse("1 + 1 == 2")) + + # Filesystem + Tree.convert(pathlib.Path("abstracttree")) + + # Zipfile + Tree.convert(zipfile.ZipFile("eclipse.jar")) + + # Nested list + Tree.convert([[1, 2, 3], [4, 5, 6]]) + +It can also construct a tree by ducktyping on ``parent`` and ``children`` attributes:: + + # Works on objects by anytree, bigtree and littletree + Tree.convert(anytree.Node('node')) + +Alternatively, you can use ``astree`` and explicitly specify how to find ``children`` and ``parent``:: + + # Tree from json-data + data = {"name": "a", + "children": [ + {"name": "b", "children": []}, + {"name": "c", "children": []} + ]} + astree(data, children=operator.itemgetter["children"]) + + # pyqt.QtWidget + astree(widget, children=lambda w: w.children(), parent = lambda w: w.parent()) + + # Tree from treelib + astree(tree.root, children=lambda nid: tree.children(nid), parent=lambda nid: tree.parent(nid)) + + # itertree + astree(tree, children=iter, parent=lambda t: t.parent) + + # Infinite binary tree + inf_binary = astree(0, children=lambda n: (2*n + 1, 2*n + 2)) + +Traversal +---------------------------------------- + +There are 3 common ways to traverse a tree: + +Pre-order + The parent is iterated over before its children. + +Post-order + The children are iterated over before their parent. + +Level-order + Nodes closer to root are iterated over before nodes further from the root. + +All these are possible by writing one of:: + + for node, item in tree.nodes.preorder(): + ... + + for node, item in tree.nodes.postorder(): + ... + + for node, item in tree.nodes.levelorder(): + ... + +These methods return an item in addition to a node. +This item is a tuple of the following fields: + +depth + This indicates how deep the node is relative to the root of the (sub)tree iterated over. + The root of the (sub)tree always has depth 0. + To find the absolute depth of a node, use ``node.ancestors.count()``. + +index + The index of this node among its siblings in relation to its direct parent. + The first child of a parent gets index 0, the second gets index 1. + The root of the (sub)tree always gets an index of ``0`` even if it has prior siblings. + +To iterate over the descendants (the nodes without the root), similar methods are defined:: + + for descendant, item in tree.descendants.preorder(): + ... + +If the order of iteration doesn't matter an alternative way to iterate is as follows:: + + for node in tree.nodes: + ... + + for descendant in tree.descendants: + ... + + +Export +---------------------------------------- + +Pretty printing:: + + print_tree(Path()) + # . + # ├─ abstracttree + # │ ├─ abstracttree\conversions.py + # │ ├─ abstracttree\export.py + # │ ├─ abstracttree\predicates.py + # │ ├─ abstracttree\treeclasses.py + # │ └─ abstracttree\__init__.py + # ├─ LICENSE + # ├─ Makefile + # ├─ manual.txt + # ├─ pyproject.toml + # ├─ README.md + # └─ tests + # ├─ tests\test_downtree.py + # ├─ tests\test_export.py + # ├─ tests\test_mutabletree.py + # ├─ tests\test_tree.py + # ├─ tests\test_uptree.py + # └─ tests\tree_instances.py + + + +Plotting with matplotlib:: + + import matplotlib.pyplot as plt + + plot_tree(ast.parse("y = x*x + 1")) + plt.show() + +.. image:: images/tree_calc_plot.png + +Export to graphviz:: + + tree = astree(seq, children=lambda x: [x[:-2], x[1:]] if x else []) + to_graphviz(tree) + + +.. image:: images/tree_dot.png + +Export to mermaid:: + + to_mermaid(str) + +.. image:: images/str_mermaid.png + +Export to latex:: + + data = [["james", "steve"], + ["patrick", "mike", "bod", "piet"]] + to_latex(data) + +.. image:: images/latex_img.png diff --git a/pyproject.toml b/pyproject.toml index 66f8440..61313d6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,8 +41,9 @@ export = [ [project.urls] Homepage = "https://github.com/lverweijen/abstracttree" Repository = "https://github.com/lverweijen/abstracttree" +Documentation = "https://lverweijen.github.io/AbstractTree/" Issues = "https://github.com/lverweijen/abstracttree/issues" -Changelog = "https://github.com/lverweijen/abstracttree/blob/main/changes.md" +Changelog = "https://lverweijen.github.io/AbstractTree/CHANGES.html" [tool.ruff] line-length = 100 @@ -58,6 +59,11 @@ preview = true [dependency-groups] dev = [ "ruff>=0.8.2", + "sphinx-rtd-theme>=3.0.2", + "sphinx>=7.4.7", + "myst-parser>=3.0.1", + "myst>=1.0.4", + "sphinxcontrib-mermaid>=1.0.0", ] [build-system]