114 lines
3.7 KiB
Python
114 lines
3.7 KiB
Python
from __future__ import annotations
|
|
|
|
import re
|
|
from html import escape
|
|
|
|
|
|
ACTOR_PLACEHOLDER_RE = re.compile(r"\{\{\s*actor\s*:\s*([a-z0-9_\-]+)\s*\}\}", re.IGNORECASE)
|
|
PLAIN_LINK_RE = re.compile(r"(?P<label>[^\n<>()]+?) \((?P<url>https?://[^\s)]+)\)")
|
|
|
|
|
|
def build_hidden_link(config: dict) -> str:
|
|
return ""
|
|
|
|
|
|
def convert_plain_links_to_html(template: str) -> str:
|
|
def repl(match: re.Match[str]) -> str:
|
|
label = match.group("label").rstrip()
|
|
url = match.group("url")
|
|
if "<a " in label:
|
|
return match.group(0)
|
|
return f'<a href="{escape(url, quote=True)}">{escape(label)}</a>'
|
|
|
|
return PLAIN_LINK_RE.sub(repl, template)
|
|
|
|
|
|
def build_actor_phrase(actor: dict, state: dict) -> str:
|
|
actor_state = state.get("actors", {})
|
|
current = actor_state.get(actor["key"], {})
|
|
status = current.get("status", actor.get("default_status", "backstage"))
|
|
return current.get("phrase", actor.get("phrases", {}).get(status, ""))
|
|
|
|
|
|
def build_actor_line(actor: dict, state: dict) -> str:
|
|
phrase = build_actor_phrase(actor, state)
|
|
display_name = actor.get("display_html", escape(actor["display_name"]))
|
|
meta = actor.get("meta_html", escape(actor["pronouns"]))
|
|
emoji = actor.get("emoji_html", escape(actor.get("emoji", "")))
|
|
return (
|
|
f'{emoji} '
|
|
f'<a href="{escape(actor["link"], quote=True)}">{display_name}</a>'
|
|
f"{meta}{escape(phrase)}"
|
|
)
|
|
|
|
|
|
def build_actor_fragment(actor: dict, state: dict) -> str:
|
|
return escape(build_actor_phrase(actor, state))
|
|
|
|
|
|
def build_actor_lines(config: dict, state: dict, skip_keys: set[str] | None = None) -> str:
|
|
actor_lines: list[str] = []
|
|
skip_keys = skip_keys or set()
|
|
|
|
for actor in config["actors"]:
|
|
if actor["key"] in skip_keys:
|
|
continue
|
|
actor_lines.append(build_actor_line(actor, state))
|
|
|
|
actor_lines.extend(config.get("static_actor_lines_html", []))
|
|
return "\n".join(actor_lines)
|
|
|
|
|
|
def replace_actor_placeholders(template: str, config: dict, state: dict) -> tuple[str, set[str]]:
|
|
used_keys: set[str] = set()
|
|
actors_by_key = {actor["key"].lower(): actor for actor in config["actors"]}
|
|
|
|
def repl(match: re.Match[str]) -> str:
|
|
actor_key = match.group(1).lower()
|
|
actor = actors_by_key.get(actor_key)
|
|
if actor is None:
|
|
return match.group(0)
|
|
used_keys.add(actor["key"])
|
|
|
|
line_start = template.rfind("\n", 0, match.start()) + 1
|
|
line_end = template.find("\n", match.end())
|
|
if line_end == -1:
|
|
line_end = len(template)
|
|
line_text = template[line_start:line_end]
|
|
|
|
if line_text.strip().lower() == match.group(0).strip().lower():
|
|
return build_actor_line(actor, state)
|
|
|
|
return build_actor_fragment(actor, state)
|
|
|
|
return ACTOR_PLACEHOLDER_RE.sub(repl, template), used_keys
|
|
|
|
|
|
def build_default_template(config: dict) -> str:
|
|
blocks = []
|
|
for key in ("header_html", "intro_links_html", "projects_block_html", "actors_title_html"):
|
|
value = config.get(key, "").strip()
|
|
if value:
|
|
blocks.append(value)
|
|
|
|
blocks.append("{{actors}}")
|
|
|
|
for key in ("legend_html", "footer_html"):
|
|
value = config.get(key, "").strip()
|
|
if value:
|
|
blocks.append(value)
|
|
|
|
return "\n\n".join(blocks)
|
|
|
|
|
|
def build_channel_text(config: dict, state: dict) -> str:
|
|
template = state.get("template", {}).get("text") or config.get("template_text") or build_default_template(config)
|
|
template = convert_plain_links_to_html(template)
|
|
template, used_keys = replace_actor_placeholders(template, config, state)
|
|
actors_block = build_actor_lines(config, state, skip_keys=used_keys)
|
|
|
|
text = template.replace("{{actors}}", actors_block)
|
|
text = text.replace("{{hidden_link}}", "")
|
|
|
|
return text
|