Files
ahmed 5254209f7b
Deploy GitHub Pages / deploy (push) Canceled after 0s
Add team about page
2026-10-02 14:06:20 +03:00

99 lines
3.4 KiB
Python

#!/usr/bin/env python3
"""Fail when the static site contains broken or non-portable local references."""
from html.parser import HTMLParser
from pathlib import Path
from urllib.parse import unquote, urlsplit
ROOT = Path(__file__).resolve().parents[1]
SKIP_PREFIXES = ("#", "data:", "mailto:", "tel:", "javascript:")
class PageParser(HTMLParser):
def __init__(self):
super().__init__()
self.references = []
self.ids = set()
self.has_viewport = False
self.has_csp = False
def handle_starttag(self, tag, attrs):
values = dict(attrs)
element_id = values.get("id")
if element_id:
self.ids.add(element_id)
if tag == "meta" and values.get("name", "").lower() == "viewport":
self.has_viewport = True
if tag == "meta" and values.get("http-equiv", "").lower() == "content-security-policy":
self.has_csp = True
for attribute in ("href", "src", "action"):
value = values.get(attribute)
if value:
self.references.append((tag, attribute, value))
def parse_page(path):
parser = PageParser()
parser.feed(path.read_text(encoding="utf-8"))
return parser
def main():
pages = sorted(ROOT.glob("*.html"))
parsed = {page.resolve(): parse_page(page) for page in pages}
errors = []
if not (ROOT / "index.html").is_file():
errors.append("missing GitHub Pages entry point: index.html")
for page in pages:
parser = parsed[page.resolve()]
if not parser.has_viewport:
errors.append(f"{page.name}: missing viewport metadata")
if not parser.has_csp:
errors.append(f"{page.name}: missing Content Security Policy")
for tag, attribute, raw_value in parser.references:
value = raw_value.strip()
if not value or value.startswith(SKIP_PREFIXES):
continue
url = urlsplit(value)
if url.scheme or url.netloc:
if attribute != "href":
errors.append(f"{page.name}: remote dependency in {attribute}: {value}")
continue
if url.path.startswith("/"):
errors.append(f"{page.name}: root-absolute path breaks project Pages: {value}")
continue
target = (page.parent / unquote(url.path or page.name)).resolve()
try:
target.relative_to(ROOT)
except ValueError:
errors.append(f"{page.name}: reference leaves repository: {value}")
continue
if not target.exists():
errors.append(f"{page.name}: missing {tag} {attribute} target: {value}")
continue
if url.fragment and target.suffix.lower() == ".html":
target_parser = parsed.get(target)
if target_parser is None:
target_parser = parse_page(target)
parsed[target] = target_parser
if unquote(url.fragment) not in target_parser.ids:
errors.append(f"{page.name}: missing anchor target: {value}")
if errors:
print("Site validation failed:")
for error in errors:
print(f"- {error}")
raise SystemExit(1)
print(f"Site validation passed: {len(pages)} HTML pages checked.")
if __name__ == "__main__":
main()