85 lines
3.2 KiB
Python
85 lines
3.2 KiB
Python
|
|
"""Drop <img> tags in article bodies whose image is gone for good.
|
||
|
|
|
||
|
|
Two kinds of gone, same symptom:
|
||
|
|
|
||
|
|
**Local.** The same situation as assetcheck.py, one level down: the recovered
|
||
|
|
bodies kept their original /library/ references, and 22 of 200 point at files no
|
||
|
|
archive holds (checked against Wayback; the CDX API returns no captures).
|
||
|
|
|
||
|
|
**Remote.** Older posts hotlink images from other people's servers, and some of
|
||
|
|
those servers no longer exist: visualberlin.org, mutanten.org, web.mac.com,
|
||
|
|
kino.web.de, lh4.google.it, l.yimg.com. Name them in LOSTIMAGES_DEAD_HOSTS and
|
||
|
|
their images are treated exactly like a lost local file. The host is matched
|
||
|
|
rather than the URL fetched, because a build must not depend on the network and
|
||
|
|
a dead host is a fact about the world, not about this document.
|
||
|
|
|
||
|
|
Left alone either kind renders as a broken-image box, which on a phone reads as
|
||
|
|
an unexplained 40px hole between paragraphs. On a gemtext capsule it is worse:
|
||
|
|
an image becomes a visible link line, so the page advertises a dead host.
|
||
|
|
|
||
|
|
The reference is still worth keeping in the Markdown source, so this strips it
|
||
|
|
only from the generated output and leaves a comment naming the file, which keeps
|
||
|
|
the output self-documenting for anyone who later finds the originals.
|
||
|
|
"""
|
||
|
|
import os
|
||
|
|
import re
|
||
|
|
|
||
|
|
from pelican import signals
|
||
|
|
|
||
|
|
IMG = re.compile(r'<img[^>]*\bsrc="([^"]+)"[^>]*>')
|
||
|
|
REMOTE = re.compile(r'^https?://([^/]+)/', re.IGNORECASE)
|
||
|
|
ANCHOR = re.compile(r'<a[^>]*\bhref="(/library/[^"]+)"[^>]*>(.*?)</a>', re.S)
|
||
|
|
|
||
|
|
|
||
|
|
def _strip(content):
|
||
|
|
body = getattr(content, "_content", None)
|
||
|
|
if not body or "<img" not in body:
|
||
|
|
return
|
||
|
|
base = content.settings.get("PATH", "content")
|
||
|
|
dead = {h.lower().lstrip(".") for h in
|
||
|
|
content.settings.get("LOSTIMAGES_DEAD_HOSTS", [])}
|
||
|
|
lost = []
|
||
|
|
|
||
|
|
def is_lost(src):
|
||
|
|
"""True when this src can never resolve again."""
|
||
|
|
remote = REMOTE.match(src)
|
||
|
|
if remote:
|
||
|
|
host = remote.group(1).lower().split(":")[0]
|
||
|
|
# match the host and any subdomain of it, so naming
|
||
|
|
# visualberlin.org also covers www.visualberlin.org
|
||
|
|
return any(host == d or host.endswith("." + d) for d in dead)
|
||
|
|
if not src.startswith("/"):
|
||
|
|
return False
|
||
|
|
return not os.path.exists(os.path.join(base, src.lstrip("/")))
|
||
|
|
|
||
|
|
def name_of(src):
|
||
|
|
return os.path.basename(src.split("?")[0].rstrip("/")) or src
|
||
|
|
|
||
|
|
def anchor(m):
|
||
|
|
rel = m.group(1).lstrip("/")
|
||
|
|
if os.path.exists(os.path.join(base, rel)):
|
||
|
|
return m.group(0)
|
||
|
|
return m.group(2)
|
||
|
|
|
||
|
|
def sub(m):
|
||
|
|
src = m.group(1)
|
||
|
|
if not is_lost(src):
|
||
|
|
return m.group(0)
|
||
|
|
lost.append(name_of(src))
|
||
|
|
return "<!-- lost image: %s -->" % name_of(src)
|
||
|
|
|
||
|
|
body = IMG.sub(sub, body)
|
||
|
|
# a thumbnail that linked to the full-size file leaves the <a> behind, and
|
||
|
|
# its href 404s just as the <img> did
|
||
|
|
body = ANCHOR.sub(anchor, body)
|
||
|
|
if not lost:
|
||
|
|
return
|
||
|
|
# a paragraph that held nothing but the lost image would still occupy space
|
||
|
|
body = re.sub(r"<p>\s*((?:<!-- lost image: [^>]*-->\s*)+)</p>", r"\1", body)
|
||
|
|
content._content = body
|
||
|
|
content.lost_images = lost
|
||
|
|
|
||
|
|
|
||
|
|
def register():
|
||
|
|
signals.content_object_init.connect(_strip)
|