Source code for pySimBlocks.tools.generate_blocks_index
# ******************************************************************************
# pySimBlocks
# Copyright (c) 2026 Université de Lille & INRIA
# ******************************************************************************
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or (at your
# option) any later version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
# for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
# ******************************************************************************
# Authors: see Authors.txt
# ******************************************************************************
import ast
import os
import yaml
from pathlib import Path
[docs]
def iter_python_files(base_path):
"""Yield paths to all non-__init__ Python files under base_path.
Args:
base_path: Root directory to walk.
Yields:
Absolute path strings to .py files.
"""
for root, _, files in os.walk(base_path):
for f in files:
if f.endswith(".py") and f != "__init__.py":
yield os.path.join(root, f)
[docs]
def find_block_classes(filepath: str | Path) -> list[str]:
"""Return names of all classes that inherit (directly or indirectly) from Block.
Uses a name-based heuristic: a class is considered a Block subclass if
``"block"`` appears (case-insensitive) in its own name or in any ancestor
name reachable within the same file.
Args:
filepath: Path to the Python source file to analyse.
Returns:
List of class names identified as Block subclasses.
"""
with open(filepath, "r", encoding="utf-8") as f:
source = f.read()
tree = ast.parse(source)
class_parents = {}
for node in ast.walk(tree):
if isinstance(node, ast.ClassDef):
parents = []
for base in node.bases:
if isinstance(base, ast.Name):
parents.append(base.id)
elif isinstance(base, ast.Attribute):
parents.append(base.attr)
class_parents[node.name] = parents
def is_block_class(cls: str) -> bool:
visited: set[str] = set()
to_visit = [cls]
while to_visit:
current = to_visit.pop()
if current in visited:
continue
visited.add(current)
if "block" in current.lower():
return True
to_visit.extend(class_parents.get(current, []))
return False
return [cls for cls in class_parents if is_block_class(cls)]
[docs]
def generate_blocks_index() -> dict:
"""Scan the blocks directory and write the YAML block index.
For each block group (subdirectory of ``pySimBlocks/blocks/``), scans
Python files, identifies Block subclasses, and records their class name
and dotted module path. The result is written to
``pySimBlocks/project/pySimBlocks_blocks_index.yaml``.
Returns:
The generated index dict (mirrors the written YAML content).
"""
blocks_dir = Path(__file__).resolve().parents[1] / "blocks"
output_path = (
Path(__file__).resolve().parents[1]
/ "project" / "pySimBlocks_blocks_index.yaml"
)
index: dict = {}
for group in os.listdir(blocks_dir):
group_path = blocks_dir / group
if (
not group_path.is_dir()
or group.startswith("_")
or group.startswith(".")
or group == "__pycache__"
):
continue
index[group] = {}
for filepath in iter_python_files(group_path):
classes = find_block_classes(filepath)
if not classes:
continue
file_stem = Path(filepath).stem
rel_path = filepath.split("pySimBlocks")[-1].lstrip("/\\")
module_path = "pySimBlocks." + rel_path.replace("/", ".").replace("\\", ".").removesuffix(".py")
# Only one block class per file (pySimBlocks rule)
class_name = classes[0]
index[group][file_stem] = {
"class": class_name,
"module": module_path
}
with open(output_path, "w") as f:
yaml.dump(index, f, sort_keys=True)
print(f"[OK] Block index written to {output_path}")
return index
if __name__ == "__main__":
generate_blocks_index()