Skip to content

Commit

Permalink
More stable way to generate node documentation links (#4727)
Browse files Browse the repository at this point in the history
* add the get_doc_link method to nodes to generate links to its documentation
  • Loading branch information
Durman authored Nov 2, 2022
1 parent eaaee92 commit 74b41a3
Show file tree
Hide file tree
Showing 43 changed files with 122 additions and 242 deletions.
2 changes: 1 addition & 1 deletion docs/nodes/analyzer/origins.rst
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ Using matrix output for mesh instancing, looks like work of adaptive polygon nod
* Number-> :doc:`A Number </nodes/number/numbers>`
* Number-> :doc:`Easing 0..1 </nodes/number/easing>`
* Vector-> :doc:`Vector In </nodes/vector/vector_in>`
* Matrix-> :doc:`Matrix Deform </nodes/matrix/deform>`
* Matrix-> :doc:`Matrix Deform </nodes/matrix/matrix_deform>`
* Matrix-> :doc:`Matrix Apply to Mesh </nodes/matrix/apply_and_join>`
* List->List Struct-> :doc:`List Slice </nodes/list_struct/slice>`
* Viz-> :doc:`Mesh Viewer </nodes/viz/mesh_viewer>`
File renamed without changes.
File renamed without changes.
File renamed without changes.
8 changes: 4 additions & 4 deletions docs/nodes/logic/neuro_elman.rst
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ Please, study for XOR operation:

.. image:: ../../assets/nodes/logic/neuro_data_in.png

* List->List Main-> :doc:`List Delete Levels </nodes/list_main/levels>`
* List->List Main-> :doc:`List Delete Levels </nodes/list_main/delete_levels>`
* Text-> :doc:`Viewer Text MK3 </nodes/text/viewer_text_mk3>`
* Script-> :doc:`Formula </nodes/script/formula_mk5>`

Expand All @@ -113,7 +113,7 @@ Same with expected data:

.. image:: ../../assets/nodes/logic/neuro_etalon.png

* List->List Main-> :doc:`List Delete Levels </nodes/list_main/levels>`
* List->List Main-> :doc:`List Delete Levels </nodes/list_main/delete_levels>`
* Script-> :doc:`Formula </nodes/script/formula_mk5>`

**Node preparations**
Expand All @@ -125,7 +125,7 @@ Same with expected data:

.. image:: ../../assets/nodes/logic/neuro_ansumble.png

* List->List Main-> :doc:`List Delete Levels </nodes/list_main/levels>`
* List->List Main-> :doc:`List Delete Levels </nodes/list_main/delete_levels>`
* Script-> :doc:`Formula </nodes/script/formula_mk5>`

Running learning and waiting. Interrupt Studying. I had have that result:
Expand All @@ -138,6 +138,6 @@ Compare result:

.. image:: ../../assets/nodes/logic/neuro_result.png

* List->List Main-> :doc:`List Delete Levels </nodes/list_main/levels>`
* List->List Main-> :doc:`List Delete Levels </nodes/list_main/delete_levels>`
* Text-> :doc:`Viewer Text MK3 </nodes/text/viewer_text_mk3>`
* Script-> :doc:`Formula </nodes/script/formula_mk5>`
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
2 changes: 1 addition & 1 deletion docs/nodes/vector/interpolation_stripes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ Making surface with stripes separated in two groups of nodes for UVconnect node
* Generator-> :doc:`Torus </nodes/generator/torus_mk2>`
* Generator-> :doc:`Cylinder </nodes/generator/cylinder_mk2>`
* Generator-> :doc:`Sphere </nodes/generator/sphere>`
* Curves-> :doc:`Circle (Curve) </nodes/curve/circle>`
* Curves-> :doc:`Circle (Curve) </nodes/curve/curve_circle>`
* Curves-> :doc:`Curve Frame </nodes/curve/curve_frame>`
* Modifiers->Modifier Make-> :doc:`UV Connection </nodes/modifier_make/uv_connect>`
* Number-> :doc:`Number Range </nodes/number/number_range>`
Expand Down
74 changes: 51 additions & 23 deletions node_tree.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,14 @@
import time
from contextlib import contextmanager
from itertools import chain, cycle
from typing import Iterable, final
from pathlib import Path
from typing import Iterable, final, Optional

import bpy
from bpy.props import StringProperty, BoolProperty, EnumProperty
from bpy.types import NodeTree, NodeSocket

import sverchok
from sverchok.core.sv_custom_exceptions import SvNoDataError, DependencyError
import sverchok.core.events as ev
import sverchok.dependencies as sv_deps
Expand Down Expand Up @@ -643,22 +645,58 @@ def dependency_error(self):
return


class SverchCustomTreeNode(UpdateNodes, NodeUtils, NodeDependencies):
"""Base class for all nodes. Documentation of a custom node class is used
to give information about the node UI. Minimal example of a custom node:
class NodeDocumentation:
"""
Documentation of a custom node class is used
to give information about the node UI.
```py
class SvSomeOperationNode(SverchCustomTreeNode, bpy.types.Node):
\"""
Triggers: vector multiply scale # <- tags
Tooltip: This node performs some operation
Merely for illustration of node creation workflow # Description
\"""
bl_idname = 'SvSomeOperationNode' # should be added to `sverchok/index.md` file
bl_label = 'Name shown in menu'
bl_icon = 'GREASEPENCIL'
\"""
Triggers: vector multiply scale # <- tags
Tooltip: This node performs some operation
Merely for illustration of node creation workflow # Description
\"""
```
"""
_docstring = None # A cache for docstring property

@classproperty
def docstring(cls):
"""
Get SvDocstring instance parsed from node's docstring.
"""
if cls._docstring is None:
cls._docstring = SvDocstring(cls.__doc__)
return cls._docstring

def get_doc_link(self, link_type='ONLINE') -> Optional[str]:
"""Returns URL to online documentation of the node, or to GitHub
documentation, or path to a documentation file of the node, or None.
This method can be overriden by Sverchok's extensions to implement
their own way to generate the links."""
*_, node_file_name = self.__module__.rpartition('.')
node_docs = Path(sverchok.__file__).parent / 'docs' / 'nodes'
for path in node_docs.rglob('*.rst'):
doc_file_name = path.stem
if node_file_name != doc_file_name:
continue

help_url = str(path.relative_to(node_docs)).replace(' ', '_').removesuffix('.rst')
if link_type == 'ONLINE':
return f'http://nortikin.github.io/sverchok/docs/nodes/{help_url}.html'
elif link_type == 'OFFLINE':
return f'file:///{path}'
elif link_type == 'GITHUB':
return f'https://github.com/nortikin/sverchok/blob/master/docs/nodes/{help_url}.rst'
else:
raise TypeError(f"{link_type=} is not supported")
return None


class SverchCustomTreeNode(UpdateNodes, NodeUtils, NodeDependencies, NodeDocumentation):
"""Base class for all nodes.
It's possible to apply Alpha/Beta icons to sv_icon class attribute of the
node to mark a node as in development state and that it can change its
Expand All @@ -670,7 +708,6 @@ class Node:
![image](https://user-images.githubusercontent.com/28003269/194234662-2a55bb27-fa58-4935-a433-f2beed1591cd.png)
"""
_docstring = None # A cache for docstring property
sv_category = '' #: Add node to a category by its name to display with Shift+S

@final
Expand Down Expand Up @@ -722,15 +759,6 @@ def sv_internal_links(self) -> Iterable[tuple[NodeSocket, NodeSocket]]:
for link in self.internal_links:
yield link.from_socket, link.to_socket

@classproperty
def docstring(cls):
"""
Get SvDocstring instance parsed from node's docstring.
"""
if cls._docstring is None:
cls._docstring = SvDocstring(cls.__doc__)
return cls._docstring

@classmethod
def poll(cls, ntree):
"""Can be overridden to make impossible to add certain nodes either to
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
123 changes: 55 additions & 68 deletions tests/docs_tests.py
Original file line number Diff line number Diff line change
@@ -1,50 +1,56 @@

import unittest
from os.path import basename, splitext, dirname, join, exists
from os import walk
from glob import glob

import sverchok
from sverchok.utils.testing import *
from sverchok.utils.logging import debug, info, error


class DocumentationTests(SverchokTestCase):

def setUp(self):
self.node_file_names = set()
self.doc_file_names = set()

sv_root = Path(sverchok.__file__).parent
nodes_folder = sv_root / 'nodes'
for path in nodes_folder.rglob('*.py'):
file_name = path.stem
if file_name == '__init__':
continue
self.node_file_names.add(file_name)

doc_folder = sv_root / 'docs' / 'nodes'
for path in doc_folder.rglob('*.rst'):
file_name = path.stem
index_file = f'{path.parent.stem}_index'
if file_name == index_file:
continue
self.doc_file_names.add(file_name)

def get_nodes_docs_directory(self):
sv_init = sverchok.__file__
return join(dirname(sv_init), "docs", "nodes")

def test_unused_docs(self):
sv_init = sverchok.__file__
nodes_dir = join(dirname(sv_init), "nodes")
docs_dir = self.get_nodes_docs_directory()

def check_dir(directory):
dir_name = basename(directory)
bad_files = []
for doc_path in glob(join(directory, "*.rst")):
if doc_path.endswith("_index.rst"):
continue
doc_file = basename(doc_path)
doc_name, ext = splitext(doc_file)
py_name = doc_name + ".py"
py_path = join(nodes_dir, dir_name, py_name)
if not exists(py_path):
bad_files.append(doc_file)
def test_uniq_node_modules(self):
sv_root = Path(sverchok.__file__).parent
nodes_folder = sv_root / 'nodes'
modules = dict()
for path in nodes_folder.rglob('*.py'):
file_name = path.stem
if file_name == '__init__':
continue
with self.subTest(node_module=f"{file_name}.py"):
if file_name in modules:
self.fail(f'There are tow modules with the same name "{file_name}" \n'
f'File 1: {Path(modules[file_name]).relative_to(sv_root)} \n'
f'file 2: {Path(path).relative_to(sv_root)}')
else:
modules[file_name] = path

if bad_files:
error("Category %s: The following documentation files do not have respective python modules:\n%s", dir_name, "\n".join(bad_files))
self.fail("There are excessive documentation files.")

for directory, subdirs, fnames in walk(docs_dir):
with self.subTest(directory=basename(directory)):
check_dir(directory)
def test_unused_docs(self):
for doc_name in self.doc_file_names:
with self.subTest(documentation_file=f"{doc_name}.rst"):
if doc_name not in self.node_file_names:
self.fail("The documentation file does not have respective python module")

def test_node_docs_existance(self):
sv_init = sverchok.__file__
nodes_dir = join(dirname(sv_init), "nodes")
docs_dir = self.get_nodes_docs_directory()

known_problems = """
obj_edit.py
BMOperatorsMK2.py
Expand All @@ -71,42 +77,23 @@ def test_node_docs_existance(self):
limited_dissolve.py
limited_dissolve_mk2.py
mesh_separate_mk2.py
symmetrize.py
approx_subd_to_nurbs.py
vd_attr_node_mk2.py
scalar_field_point.py
quads_to_nurbs.py
location.py
sun_position.py""".split("\n")

def check_category(directory):
dir_name = basename(directory)
bad_files = []
known = []

for module_path in glob(join(directory, "*.py")):
module_file = basename(module_path)
if module_file == "__init__.py":
continue
module_name, ext = splitext(module_file)
doc_name = module_name + ".rst"
doc_path = join(docs_dir, dir_name, doc_name)
if not exists(doc_path):
if module_file in known_problems:
known.append(module_file)
else:
bad_files.append(module_file)

category = dir_name

if known:
explicitly_missing = "\n".join(known)
info(f"{category=}: Tolerating missing documentation for the following nodes for now:\n{explicitly_missing=}")

if bad_files:
missing = "\n".join(bad_files)
self.fail(f"Not all nodes of {category=} have corresponding documentation; \n{missing=}")
sun_position.py
flip_surface.py
ruled_surface.py
""".split("\n")

for directory, subdirs, fnames in walk(nodes_dir):
with self.subTest(directory=basename(directory)):
check_category(directory)
for module_name in self.node_file_names:
with self.subTest(node_module=f"{module_name}.py"):
if module_name not in self.doc_file_names:
if f'{module_name}.py' not in known_problems:
self.fail(f"Node module={module_name}.py does not have"
f" documentation")
else:
if f'{module_name}.py' in known_problems:
self.fail(f"Exclude the node module={module_name}.py"
f" from the Known Problems list")
Loading

0 comments on commit 74b41a3

Please sign in to comment.