#!/usr/bin/env python3.14# -*- coding: utf-8 -*-"""Versioned asset URLs: `/assets/site.css?v=<hash>` — a URL that names its own bytes (shared).Caddy serves `/assets` and `/vendor` from a copy of this package's `content/` tree. Anunversioned path must be revalidated on every use, because the same path names differentbytes after a redeploy (docs/web-server-guide.md § 11.10). A path that carries a hash ofits file's contents never does: the edge marks a request with `?v=` `immutable` for ayear, and a redeploy that changes the file changes the URL the pages ask for.The templates write `{{ asset('/assets/site.css') }}`, never the bare path;`tests/test_assets.py` holds them to it. The hash is read from the installed package — thesame checkout `redeploy-web` copies to the edge, and the copy goes first, so the bytes arein place before a page names them. A file the package lacks gets its bare path, and with itthe revalidated rule: a missing hash costs a conditional request, never a stale file.Not covered: what a script imports by itself (site.js's `import('/assets/editor.js')`,the editor's CodeMirror modules). Those URLs are in the files, not the templates, and keepthe revalidated rule."""from__future__importannotations__all__=['CONTENT_DIR','asset_url','install']importhashlibfromfunctoolsimportcachefrompathlibimportPathimportjinja2#: The static tree the edge serves, as installed with this package.CONTENT_DIR:Path=Path(__file__).resolve().parent/'content'#: Hex digits of the content hash: enough to tell two builds of one file apart._DIGITS:int=12@cachedefasset_url(path:str)->str:"""*path* (`/assets/…` or `/vendor/…`) with `?v=` its contents' hash, or bare if unreadable. Cached for the life of the process: the installed files change only with a redeploy, which restarts it. """try:data=(CONTENT_DIR/path.lstrip('/')).read_bytes()exceptOSError:returnpathreturnf'{path}?v={hashlib.sha256(data).hexdigest()[:_DIGITS]}'definstall(env:jinja2.Environment)->None:"""Make `asset()` available to every template *env* renders."""env.globals['asset']=asset_url