Files
LEDMatrix/test/test_doc_links.py
T
Claude b428ae3eae fix: address CodeRabbit review findings
Verified each finding against the code; fixes for the valid ones:

- install_dependencies_apt.py: the installer listed 'freetype', but the
  declared dependency is freetype-py — an apt miss would pip-install the
  wrong PyPI package. Now installs freetype-py with an import-name
  mapping (pre-existing bug, surfaced by the review).
- api_v3.py: pixel_mapper_config is validated as a string before being
  saved to display.hardware (JSON callers could previously store an
  object/list the matrix library can't use).
- .cursorrules: the Plugin Loading Process and File Organization
  sections still said discovery scans plugins/ — now consistent with the
  corrected overview (configured directory, default plugin-repos/).
- README.md: removed the stale '(except the core calendar)' claim — no
  core calendar exists in src/ — and qualified the plugin inventory
  (official plugins in the monorepo; third-party from their own repos).
- CONFIG_REFERENCE.md: hardware_mapping now shows the code fallback
  (adafruit-hat-pwm) alongside the template value.
- PLUGIN_REGISTRY_SETUP_GUIDE.md: check_plugin.py takes --plugin, not a
  positional id.
- scripts/fix_perms/fix_*.sh: exec bits set so the documented
  'sudo ./...' invocations work.
- Guard tests hardened: template guard now catches multi-line
  render_template() calls; widget guard parses actual <script> src
  values and fails if the widgets dir goes missing; type hints and
  docstrings added per repo coding guidelines.

Skipped with reasons (noted on the PR): limit_refresh_rate_hz 100-vs-90
is documented as intentional in CONFIG_REFERENCE.md; the psutil comment
already names the enforcing manifest; docs/archive/ findings are out of
scope per the docs policy (archive may rot).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SXb4mKcAkVaxkeTb3YnAdr
2026-08-06 01:32:23 +00:00

44 lines
1.6 KiB
Python

"""Guard: relative markdown links in active docs must resolve.
Scans repo-root *.md and docs/ (excluding docs/archive/, which is allowed
to rot). External URLs, mailto links, and pure anchors are skipped, as are
links inside fenced code blocks.
"""
import re
from pathlib import Path
from typing import Iterator
PROJECT_ROOT = Path(__file__).resolve().parent.parent
LINK_RE = re.compile(r'\[[^\]]*\]\(([^)\s]+)\)')
FENCE_RE = re.compile(r'^(```|~~~)')
def _md_files() -> Iterator[Path]:
"""Yield active markdown files (repo root + docs/, excluding docs/archive/)."""
yield from PROJECT_ROOT.glob('*.md')
for path in PROJECT_ROOT.glob('docs/**/*.md'):
if 'archive' not in path.parts:
yield path
def test_relative_markdown_links_resolve() -> None:
"""Every relative markdown link outside code fences must resolve on disk."""
broken = []
for md in _md_files():
in_fence = False
for lineno, line in enumerate(md.read_text(encoding='utf-8').splitlines(), 1):
if FENCE_RE.match(line.strip()):
in_fence = not in_fence
continue
if in_fence:
continue
for target in LINK_RE.findall(line):
if target.startswith(('http://', 'https://', 'mailto:', '#')):
continue
resolved = (md.parent / target.split('#')[0]).resolve()
if not resolved.exists():
broken.append(
f'{md.relative_to(PROJECT_ROOT)}:{lineno} -> {target}'
)
assert not broken, 'Broken relative markdown links:\n' + '\n'.join(broken)