60 lines
1.9 KiB
Python
60 lines
1.9 KiB
Python
#!/usr/bin/env python3
|
|
import re
|
|
import sys
|
|
from html.parser import HTMLParser
|
|
from pathlib import Path
|
|
from urllib.parse import urlparse
|
|
|
|
ROOT = Path(__file__).resolve().parent.parent
|
|
INDEX = ROOT / "index.html"
|
|
SKIP = ("#", "mailto:", "tel:", "data:", "javascript:")
|
|
FETCH_RELS = {"stylesheet", "icon", "preload", "modulepreload", "manifest", "apple-touch-icon"}
|
|
errors = []
|
|
|
|
|
|
def check(ref, base, where, fetched):
|
|
if not ref or ref.startswith(SKIP):
|
|
return
|
|
if ref.startswith(("http://", "https://", "//")):
|
|
if fetched:
|
|
errors.append(f"{where}: external request {ref}")
|
|
return
|
|
path = urlparse(ref).path
|
|
target = ROOT / path.lstrip("/") if path.startswith("/") else base.parent / path
|
|
if not target.exists():
|
|
errors.append(f"{where}: missing file {ref}")
|
|
|
|
|
|
class Page(HTMLParser):
|
|
def handle_starttag(self, tag, attrs):
|
|
a = dict(attrs)
|
|
refs = []
|
|
if tag in ("script", "img", "source", "iframe", "video", "audio"):
|
|
refs.append(a.get("src"))
|
|
for part in (a.get("srcset") or "").split(","):
|
|
if part.strip():
|
|
refs.append(part.split()[0])
|
|
elif tag == "link" and FETCH_RELS & set((a.get("rel") or "").split()):
|
|
refs.append(a.get("href"))
|
|
for ref in refs:
|
|
check(ref, INDEX, "index.html", True)
|
|
if tag == "a":
|
|
check(a.get("href"), INDEX, "index.html", False)
|
|
|
|
|
|
for name in ("index.html", "favicon.svg", "robots.txt"):
|
|
if not (ROOT / name).is_file():
|
|
errors.append(f"missing required file: {name}")
|
|
|
|
if INDEX.is_file():
|
|
Page().feed(INDEX.read_text(encoding="utf-8"))
|
|
|
|
for css in ROOT.glob("assets/*.css"):
|
|
for ref in re.findall(r"url\(\s*['\"]?([^'\")]+)", css.read_text(encoding="utf-8")):
|
|
check(ref, css, str(css.relative_to(ROOT)), True)
|
|
|
|
if errors:
|
|
print("\n".join(errors))
|
|
sys.exit(1)
|
|
print("site check passed")
|