import dataclasses import os import re import typing as t import yaml with open("index.html") as file: template = file.read() with open("res/arrow-path.txt") as file: arrow_path = file.read().strip() with open("res/i18n.yml") as file: i18n = yaml.safe_load(file) def htmlify(text: str) -> str: return ( text.replace("&", "&") .replace("<", "<") .replace(">", ">") .replace("\n", "
") ) # Awful awful awful code, but it does the job. @dataclasses.dataclass class BBLiteral: text: str def to_html(self) -> str: return htmlify(self.text) TokenRaw = t.Tuple[t.Literal["start", "end", "text"], str] @dataclasses.dataclass class BBTag: name: str children: list["BBLiteral | BBTag"] def to_html(self) -> str: if self.name == "root": return "".join(child.to_html() for child in self.children) elif self.name == "b": inner = "".join(child.to_html() for child in self.children) return f"{inner}" elif self.name == "i": inner = "".join(child.to_html() for child in self.children) return f"{inner}" elif self.name == "a": assert len(self.children) == 1 and isinstance( self.children[0], BBLiteral ), "[a] tag must contain exactly one literal child" href = "https://creativecommons.org/public-domain/cc0/" inner = "".join(child.to_html() for child in self.children) return f'{inner}' else: raise ValueError(f"Unknown tag: {self.name}") class BBParser: source: str current_tags: list[str] pos: int def __init__(self, source: str): self.source = source self.current_tags = [] self.pos = 0 def render(self) -> BBTag: root = BBTag(name="root", children=[]) stack = [root] for kind, value in self.parse(): if kind == "text": stack[-1].children.append(BBLiteral(text=value)) elif kind == "start": new_tag = BBTag(name=value, children=[]) stack[-1].children.append(new_tag) stack.append(new_tag) elif kind == "end": assert stack.pop().name == value, "Mismatched closing tag" assert len(stack) == 1, "Unclosed tags remain" return root def parse( self, ) -> t.Generator[TokenRaw, None, None]: while self.pos < len(self.source): yield self.next_token() def next_token(self) -> TokenRaw: if self.source[self.pos] == "[": end_tag_pos = self.source.find("]", self.pos) if end_tag_pos == -1: raise ValueError("Unclosed tag") tag = self.source[self.pos + 1 : end_tag_pos] if tag.startswith("/"): kind = "end" tag = tag[1:] assert self.current_tags.pop() == tag, "Mismatched closing tag" else: kind = "start" self.current_tags.append(tag) assert ( len([i for i in self.current_tags if i == "a"]) <= 1 ), "Nested [a] tags" self.pos = end_tag_pos + 1 return (kind, tag) else: next_tag_pos = self.source.find("[", self.pos) if next_tag_pos == -1: next_tag_pos = len(self.source) text = self.source[self.pos : next_tag_pos] self.pos = next_tag_pos return ("text", text) def htmlify_bbcode(text: str) -> str: parser = BBParser(text) parsed = parser.render() return parsed.to_html() def format(template: str, values: dict[str, str]) -> str: output = "" i = 0 while i < len(template): if template[i:].startswith("{{"): end_pos = template.find("}}", i) if end_pos == -1: raise ValueError("Unclosed placeholder") key = template[i + 2 : end_pos].strip() if key not in values: raise KeyError(f"Missing value for key '{key}'") output += values[key] i = end_pos + 2 else: output += template[i] i += 1 return output # English is my native language and the one I can advocate for the accuracy of # the content of this website the most, so it is the canonical translation. I'm # not trying to make the claim that English is in any way the "default". canonical_lang = "en" LANG_RE = re.compile(r"^[a-z]{2,3}(_[A-Z]{2,3})?$") for lang in i18n["locales"]: if not LANG_RE.match(lang): raise ValueError(f"Invalid language code: '{lang}'") alternates_list = [] for lang in i18n["locales"]: href = ( "https://eunakria.com/ln-s/" if lang == canonical_lang else f"https://eunakria.com/ln-s/{lang}/index.html" ) alternates_list.append( (lang, f'') ) for lang, texts in i18n["locales"].items(): fallbacks = [texts] if "_" in lang: base_lang = lang.split("_")[0] if base_lang in i18n["locales"]: fallbacks.append(i18n["locales"][base_lang]) fallbacks.append(i18n["locales"][canonical_lang]) def tr(key: str) -> str: for fb in fallbacks: if key in fb: return fb[key] raise KeyError(f"Missing translation for key '{key}' in language '{lang}'") description = htmlify(tr("description")) file_annotation = htmlify(tr("file_annotation")) link_annotation = htmlify(tr("link_annotation")) file = htmlify(tr("file")) link = htmlify(tr("link")) deed = htmlify_bbcode(tr("deed")) alternates_str = "\n".join( alt for alt_lang, alt in alternates_list if alt_lang != lang ) output = format( template, { "lang": lang, "arrow_path": arrow_path, "description": description, "file_annotation": file_annotation, "link_annotation": link_annotation, "file": file, "link": link, "deed": deed, "alternates": alternates_str, }, ) output_path = "index.html" if lang == canonical_lang else f"{lang}/index.html" output_path = f"dist/{output_path}" os.makedirs(os.path.dirname(output_path), exist_ok=True) with open(output_path, "w") as file: file.write(output)