"""Turn verified entries into a genanki package (flip + self-grade cards).""" from __future__ import annotations import hashlib from html import escape from typing import TYPE_CHECKING import genanki if TYPE_CHECKING: from python_pkg.js_anki.models import Entry _ID_SPACE = 1 << 31 CSS = """ .card { font-family: system-ui, sans-serif; font-size: 20px; text-align: left; color: #e6e6e6; background: #1b1d21; padding: 12px; } pre { background: #25282e; padding: 10px; border-radius: 6px; overflow-x: auto; white-space: pre-wrap; } code { font-family: ui-monospace, Menlo, Consolas, monospace; font-size: 17px; } .gotcha { color: #f0b429; margin-top: 10px; } .res { color: #7bd88f; } """ def stable_id(key: str) -> int: """Deterministic 31-bit id so re-imports update instead of duplicating.""" return int(hashlib.sha256(key.encode()).hexdigest()[:8], 16) % _ID_SPACE def build_model() -> genanki.Model: """The single two-field note type shared by all cards.""" return genanki.Model( stable_id("js_anki:model:v1"), "JS method (js_anki)", fields=[{"name": "Front"}, {"name": "Back"}], templates=[ { "name": "Card", "qfmt": "{{Front}}", "afmt": '{{FrontSide}}
{{Back}}', } ], css=CSS, ) def deck_name(entry: Entry) -> str: """Subdeck for an entry: JS::Array, JS::Array::Rare, JS::Extras, ...""" base = f"JS::{entry.family.capitalize()}" if entry.family != "extras" and entry.tier == "rare": return f"{base}::Rare" return base def _code(text: str) -> str: return f"
{escape(text)}
" def _gotcha(entry: Entry) -> str: return f'
⚠ {escape(entry.gotcha)}
' if entry.gotcha else "" def cards_for(entry: Entry) -> list[tuple[str, str, str]]: """Return (kind, front, back) triples for one entry.""" name = f"{escape(entry.method)}" task_back = ( f"{_code(entry.call)}{_code(entry.example)}" f'
→ {escape(entry.expected)}
{_gotcha(entry)}' ) cards = [("task", escape(entry.task), task_back)] if entry.mutates is not None: mut = ( "Yes — mutates the original" if entry.mutates else "No — original untouched" ) cards.append( ( "behaviour", f"{name}: does it mutate the original, and what does it return?", f"{mut}
Returns: {escape(entry.returns)}{_gotcha(entry)}", ) ) if entry.signature: cards.append( ("args", f"{name}: argument order / signature?", _code(entry.signature)) ) return cards def build_package(entries: list[Entry]) -> tuple[genanki.Package, int]: """Build the .apkg package; returns (package, card_count).""" model = build_model() decks: dict[str, genanki.Deck] = {} count = 0 for entry in entries: name = deck_name(entry) deck = decks.setdefault(name, genanki.Deck(stable_id(f"deck:{name}"), name)) for kind, front, back in cards_for(entry): deck.add_note( genanki.Note( model=model, fields=[front, back], tags=[entry.family, entry.tier], guid=genanki.guid_for("js_anki", entry.uid, kind), ) ) count += 1 return genanki.Package(list(decks.values())), count