ai-agent-book 精选快照(<2MB 代码与文档,来自 github.com/bojieli/ai-agent-book)
Build latest book artifacts / build (push) Canceled after 0s
dependency resolution / resolve (3.11) (push) Canceled after 0s
dependency resolution / resolve (3.13) (push) Canceled after 0s
deploy-pages / build (push) Canceled after 0s
deploy-pages / deploy (push) Canceled after 0s
i18n consistency check / check (push) Canceled after 0s
provider adoption tests / test (chapter2/context-compression) (push) Canceled after 0s
provider adoption tests / test (chapter2/prompt-injection) (push) Canceled after 0s
provider adoption tests / test (chapter2/system-hint) (push) Canceled after 0s
provider adoption tests / test (chapter3/log-sanitization) (push) Canceled after 0s
web-search-agent tests / test (push) Canceled after 0s
web-search-agent tests / agentbook (push) Canceled after 0s
Build latest book artifacts / build (push) Canceled after 0s
dependency resolution / resolve (3.11) (push) Canceled after 0s
dependency resolution / resolve (3.13) (push) Canceled after 0s
deploy-pages / build (push) Canceled after 0s
deploy-pages / deploy (push) Canceled after 0s
i18n consistency check / check (push) Canceled after 0s
provider adoption tests / test (chapter2/context-compression) (push) Canceled after 0s
provider adoption tests / test (chapter2/prompt-injection) (push) Canceled after 0s
provider adoption tests / test (chapter2/system-hint) (push) Canceled after 0s
provider adoption tests / test (chapter3/log-sanitization) (push) Canceled after 0s
web-search-agent tests / test (push) Canceled after 0s
web-search-agent tests / agentbook (push) Canceled after 0s
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Keep reader-facing site assets and JSON files linked from rendered Markdown."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from html.parser import HTMLParser
|
||||
from pathlib import Path
|
||||
from urllib.parse import unquote, urlsplit
|
||||
|
||||
import markdown
|
||||
|
||||
ALWAYS_PUBLISHED_SUFFIXES = {
|
||||
".css",
|
||||
".jpeg",
|
||||
".jpg",
|
||||
".js",
|
||||
".md",
|
||||
".png",
|
||||
".svg",
|
||||
".txt",
|
||||
}
|
||||
|
||||
|
||||
class LinkCollector(HTMLParser):
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self.targets: list[str] = []
|
||||
|
||||
def handle_starttag(self, tag: str, attrs: list[tuple[str, str | None]]) -> None:
|
||||
attribute = (
|
||||
"src"
|
||||
if tag in ("img", "script", "iframe", "source", "embed", "audio", "video")
|
||||
else "href"
|
||||
if tag in ("a", "link")
|
||||
else None
|
||||
)
|
||||
if attribute is None:
|
||||
return
|
||||
values = dict(attrs)
|
||||
if values.get(attribute):
|
||||
self.targets.append(values[attribute] or "")
|
||||
|
||||
|
||||
def rendered_links(text: str) -> list[str]:
|
||||
collector = LinkCollector()
|
||||
collector.feed(markdown.markdown(text, extensions=["fenced_code"]))
|
||||
return collector.targets
|
||||
|
||||
|
||||
def safe_regular_file(path: Path, root: Path) -> Path | None:
|
||||
try:
|
||||
resolved = path.resolve(strict=True)
|
||||
resolved.relative_to(root)
|
||||
except (FileNotFoundError, RuntimeError, ValueError):
|
||||
return None
|
||||
return resolved if resolved.is_file() else None
|
||||
|
||||
|
||||
def linked_json_files(root: Path) -> set[Path]:
|
||||
linked: set[Path] = set()
|
||||
for source in root.rglob("*.md"):
|
||||
markdown_file = safe_regular_file(source, root)
|
||||
if markdown_file is None:
|
||||
continue
|
||||
text = markdown_file.read_text(encoding="utf-8", errors="replace")
|
||||
for target in rendered_links(text):
|
||||
parsed = urlsplit(target)
|
||||
if parsed.scheme or parsed.netloc:
|
||||
continue
|
||||
decoded = unquote(parsed.path)
|
||||
if not decoded.lower().endswith(".json"):
|
||||
continue
|
||||
candidate = root / decoded.lstrip("/") if decoded.startswith("/") else source.parent / decoded
|
||||
resolved = safe_regular_file(candidate, root)
|
||||
if resolved is not None:
|
||||
linked.add(resolved)
|
||||
return linked
|
||||
|
||||
|
||||
def clean(root: Path) -> None:
|
||||
root = root.resolve(strict=True)
|
||||
keep_json = linked_json_files(root)
|
||||
for path in root.rglob("*"):
|
||||
if not (path.is_file() or path.is_symlink()):
|
||||
continue
|
||||
resolved = safe_regular_file(path, root)
|
||||
keep_regular_asset = resolved is not None and path.suffix.lower() in ALWAYS_PUBLISHED_SUFFIXES
|
||||
if keep_regular_asset or resolved in keep_json:
|
||||
continue
|
||||
path.unlink()
|
||||
|
||||
|
||||
def main() -> None:
|
||||
if len(sys.argv) != 2:
|
||||
raise SystemExit("usage: clean_site_files.py DEST")
|
||||
clean(Path(sys.argv[1]))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user