Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
1 change: 0 additions & 1 deletion .gitattributes
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
*.step binary
2ARM_ROBOT/**/*.sh text eol=lf
13 changes: 0 additions & 13 deletions .github/dependabot.yml

This file was deleted.

36 changes: 0 additions & 36 deletions .github/pull_request_template.md

This file was deleted.

119 changes: 119 additions & 0 deletions .github/scripts/check_markdown_links.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
"""Fail when a relative Markdown link or #anchor in this repository does not resolve.

Usage: python3 .github/scripts/check_markdown_links.py [repo_root]

Paths are matched with exact letter case, the way GitHub serves them.
Links inside fenced code blocks and absolute URLs are not checked.
"""
from __future__ import annotations

import os
import re
import sys
import urllib.parse
from pathlib import Path

FENCE = re.compile(r"^(\s*)(```|~~~)")
INLINE = re.compile(
r"(!?)\[((?:[^\[\]]|\[[^\[\]]*\])*)\]\(\s*(<[^>]*>|[^)\s]+)((?:\s+(?:\"[^\"]*\"|'[^']*'))?)\s*\)"
)
REF_DEF = re.compile(r"^ {0,3}\[[^\]]+\]:[ \t]*(<[^>]*>|\S+)")
HTML_ATTR = re.compile(r"\b(?:src|href)\s*=\s*([\"'])([^\"']*)\1", re.I)
SKIP = ("http://", "https://", "mailto:", "tel:", "data:", "ftp:", "/")


def prose_chunks(text: str):
in_code, marker, buf = False, "", []
for line in text.splitlines():
m = FENCE.match(line)
if not in_code and m:
in_code, marker = True, m.group(2)
continue
if in_code:
if line.strip().startswith(marker):
in_code = False
continue
buf.append(line)
return "\n".join(buf)


def slug(heading: str) -> str:
text = re.sub(r"!?\[([^\]]*)\]\([^)]*\)", r"\1", heading)
text = re.sub(r"<[^>]+>", "", text).replace("`", "").replace("*", "")
return re.sub(r"[^\w\- ]", "", text.strip().lower()).replace(" ", "-")


def anchors(md: Path, cache: dict) -> set[str]:
if md not in cache:
found, seen = set(), {}
prose = prose_chunks(md.read_text(encoding="utf-8", errors="ignore"))
for line in prose.splitlines():
m = re.match(r"^\s{0,3}#{1,6}\s+(.*?)\s*#*\s*$", line)
if m:
s = slug(m.group(1))
n = seen.get(s, 0)
found.add(s if n == 0 else f"{s}-{n}")
seen[s] = n + 1
found |= set(re.findall(r"<a\s+(?:id|name)=[\"']([^\"']+)[\"']", prose))
cache[md] = {a.lower() for a in found}
return cache[md]


def exists_exact(root: Path, rel: Path) -> bool:
current = root
for part in rel.parts:
if part in ("", "."):
continue
if part == "..":
current = current.parent
continue
try:
names = os.listdir(current)
except (FileNotFoundError, NotADirectoryError):
return False
if part not in names:
return False
current = current / part
return True


def main() -> int:
root = Path(sys.argv[1] if len(sys.argv) > 1 else ".").resolve()
broken, cache = [], {}
for md in sorted(root.rglob("*.md")):
if ".git" in md.parts:
continue
prose = prose_chunks(md.read_text(encoding="utf-8", errors="ignore"))
targets = [m.group(3) for m in INLINE.finditer(prose)]
targets += [m.group(1) for line in prose.splitlines() if (m := REF_DEF.match(line))]
targets += [m.group(2) for m in HTML_ATTR.finditer(prose)]
here = md.relative_to(root).as_posix()
for t in targets:
t = t[1:-1] if t.startswith("<") and t.endswith(">") else t
if not t or t.lower().startswith(SKIP) or "://" in t:
continue
path, _, anchor = t.partition("#")
path = path.split("?")[0]
if not path:
if anchor and urllib.parse.unquote(anchor).lower() not in anchors(md, cache):
broken.append(f"{here}: {t} (anchor)")
continue
rel_from_root = Path(os.path.normpath(md.parent.relative_to(root) / urllib.parse.unquote(path)))
if rel_from_root.parts and rel_from_root.parts[0] == "..":
broken.append(f"{here}: {t} (outside repository)")
continue
if not exists_exact(root, rel_from_root):
broken.append(f"{here}: {t}")
continue
target = root / rel_from_root
if anchor and target.suffix.lower() == ".md":
if urllib.parse.unquote(anchor).lower() not in anchors(target, cache):
broken.append(f"{here}: {t} (anchor)")
for b in broken:
print(f"broken link: {b}")
print(f"Markdown links checked; broken: {len(broken)}")
return 1 if broken else 0


if __name__ == "__main__":
sys.exit(main())
41 changes: 0 additions & 41 deletions .github/workflows/cowork-smoke.yml

This file was deleted.

47 changes: 47 additions & 0 deletions .github/workflows/hardware-free-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
name: Hardware-free tests

on:
pull_request:
branches:
- main
workflow_dispatch:

permissions:
contents: read

concurrency:
group: hardware-free-tests-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

jobs:
hardware-free-tests:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Check out repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

- name: Set up Python
uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
with:
python-version: "3.12"

- name: Install NumPy for the sim-first evaluator tests
run: "python3 -m pip install --disable-pip-version-check --only-binary=:all: numpy==2.3.5"

- name: SO-101 sim-first unit tests
working-directory: mini-projects
env:
PYTHONDONTWRITEBYTECODE: "1"
run: python3 -m unittest discover -s dapier_sim_first/test -v

- name: CardBench unit tests
working-directory: mini-projects/casino_dealer
env:
PYTHONDONTWRITEBYTECODE: "1"
run: python3 -m unittest discover -s test -v

- name: Markdown links
run: python3 .github/scripts/check_markdown_links.py
64 changes: 0 additions & 64 deletions .github/workflows/hardware-ready.yml

This file was deleted.

Loading
Loading