testsAndMisc-archive/python_pkg/repo_explorer/_discovery.py

96 lines
2.7 KiB
Python
Raw Normal View History

"""Project discovery helpers and shared constants for Repo Explorer."""
from __future__ import annotations
from pathlib import Path
import re
import shutil
from typing import cast
# Strip ANSI/VT100 escape sequences so the Text widget shows plain text
_ANSI_ESCAPE = re.compile(r"\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])")
def _strip_ansi(text: str) -> str:
return _ANSI_ESCAPE.sub("", text)
def _find_terminal() -> list[str]:
"""Return argv prefix for the first available terminal emulator."""
candidates = [
("kitty", ["kitty", "--"]),
("alacritty", ["alacritty", "-e"]),
("konsole", ["konsole", "-e"]),
("gnome-terminal", ["gnome-terminal", "--"]),
("xfce4-terminal", ["xfce4-terminal", "-x"]),
("xterm", ["xterm", "-e"]),
]
for exe, args in candidates:
if shutil.which(exe):
return args
return []
REPO_ROOT = Path(__file__).resolve().parent.parent.parent
IGNORED_DIRS = {
".git",
".venv",
"__pycache__",
"node_modules",
"build",
"target",
".mypy_cache",
".ruff_cache",
}
def _is_ignored(path: Path) -> bool:
return any(part in IGNORED_DIRS for part in path.parts)
def find_projects(root: Path) -> list[dict[str, object]]:
"""Return every directory under *root* that contains a run.sh."""
projects: list[dict[str, object]] = []
for run_sh in sorted(root.rglob("run.sh")):
if _is_ignored(run_sh):
continue
proj_dir = run_sh.parent
rel = proj_dir.relative_to(root)
projects.append({"path": proj_dir, "rel": rel, "name": proj_dir.name})
return projects
def _desc_from_run_sh(run_sh: Path) -> str:
"""Extract leading comment block from run.sh as a description."""
comments: list[str] = []
for line in run_sh.read_text(errors="replace").splitlines():
s = line.strip()
if s.startswith("#!"):
continue
if s.startswith("#"):
comments.append(s[1:].strip())
elif comments:
break
return " ".join(comments)[:300] if comments else ""
def get_description(project_path: Path) -> str:
"""Return a short description from README.md or leading run.sh comments."""
for readme_name in ("README.md", "README.txt", "readme.md"):
readme = project_path / readme_name
if readme.exists():
text = readme.read_text(errors="replace")
for line in text.splitlines():
stripped = line.strip().lstrip("#").strip()
if stripped:
return cast("str", stripped[:300])
run_sh = project_path / "run.sh"
if run_sh.exists():
desc = _desc_from_run_sh(run_sh)
if desc:
return desc
return "(no description)"