Integrate task runner into python tools

This commit is contained in:
ajp_anton
2026-08-08 17:21:50 +00:00
parent f8fb1eed75
commit 56d6194324
22 changed files with 1137 additions and 7 deletions
+107
View File
@@ -0,0 +1,107 @@
"""Safe, non-executing task discovery."""
from __future__ import annotations
import ast
from dataclasses import dataclass
from pathlib import Path
def display_name(value: str) -> str:
return value.replace("_", " ")
@dataclass(frozen=True)
class Task:
id: str
group_id: str
filename: str
path: Path
display_name: str
description: str | None
@dataclass(frozen=True)
class Group:
id: str
path: Path
display_name: str
tasks: tuple[Task, ...]
class TaskCatalog:
"""Discovers only direct Python children of non-hidden group directories."""
def __init__(self, task_root: Path) -> None:
self.task_root = task_root
def groups(self) -> tuple[Group, ...]:
if not self.task_root.is_dir():
return ()
groups = []
for group_path in sorted(self.task_root.iterdir(), key=lambda path: path.name):
if (
group_path.name.startswith((".", "_"))
or group_path.is_symlink()
or not group_path.is_dir()
):
continue
tasks = tuple(self._tasks_in_group(group_path))
groups.append(
Group(
id=group_path.name,
path=group_path,
display_name=display_name(group_path.name),
tasks=tasks,
)
)
return tuple(groups)
def group(self, group_id: str) -> Group | None:
return next((group for group in self.groups() if group.id == group_id), None)
def task(self, task_id: str) -> Task | None:
for group in self.groups():
for task in group.tasks:
if task.id == task_id:
return task
return None
def _tasks_in_group(self, group_path: Path) -> list[Task]:
tasks = []
for path in sorted(group_path.iterdir(), key=lambda candidate: candidate.name):
if (
path.name.startswith((".", "_"))
or path.is_symlink()
or not path.is_file()
or path.suffix != ".py"
):
continue
tasks.append(
Task(
id=f"{group_path.name}/{path.name}",
group_id=group_path.name,
filename=path.name,
path=path,
display_name=display_name(path.stem),
description=read_description(path),
)
)
return tasks
def read_description(path: Path) -> str | None:
"""Return the first docstring line without importing or executing a task."""
try:
module = ast.parse(path.read_text(encoding="utf-8"), filename=str(path))
except (OSError, SyntaxError, UnicodeDecodeError):
return None
docstring = ast.get_docstring(module, clean=True)
if not docstring:
return None
return next(
(line.strip() for line in docstring.splitlines() if line.strip()),
None,
)