#!/usr/bin/env python3
"""Builds the Anki packages the app's import tests read, with Anki itself.
The `anki` Python package is Anki's own backend, so the packages are exactly
what Anki exports: one collection, exported in the latest format, in the
legacy format ("Support older Anki versions"), and repacked the way Anki
before 2.1.50 wrote packages (only `collection.anki2`, no `meta`).
The collection holds, on purpose:
- a Basic note with bold text, a line break, an image, and two tags, one of
them hierarchical (deck "Japanese");
- a "Basic (and reversed card)" note with a sound, both its cards answered,
one of them first wrong (deck "Japanese::Animals");
- a Cloze note with two deletions, never reviewed;
- a Basic note with a list and HTML entities ("Japanese::Animals");
- a manual reschedule in the review log (no answer).
A second deck (`anki_furigana*.apkg`) has a note type that shows its
Reading through Anki's furigana filter, and the same text in its Expression.
Run from the repository root, in a virtual environment with Anki's package:
python3 -m venv /tmp/anki && /tmp/anki/bin/pip install anki==26.9.3
/tmp/anki/bin/python tool/anki/make_fixtures.py
It rewrites halkyon_learn_flutter/test/feature/anki/fixtures/*.apkg. Ids and
times differ on every run; the tests don't depend on them.
"""
import io
import os
import tempfile
import zipfile
from pathlib import Path
from anki.collection import Collection, ExportAnkiPackageOptions
from anki.decks import DeckId
OUT = Path("halkyon_learn_flutter/test/feature/anki/fixtures")
# Valid files, so the packages also serve to try the import by hand: a 1x1
# PNG (one orange pixel), and 0.26 s of silence as an MP3 (ten MPEG-1 Layer
# III frames, 128 kbit/s, 44.1 kHz, mono, whose zeroed side information
# decodes to silence).
PNG = bytes.fromhex(
"89504e470d0a1a0a0000000d49484452000000010000000108060000001f15c489"
"0000000d49444154789c63f8dfc0f01f000680027f104c1be10000000049454e44ae426082"
)
MP3 = (bytes.fromhex("fffb90c4") + bytes(413)) * 10
AGAIN, GOOD, EASY = 0, 2, 3
def main():
OUT.mkdir(parents=True, exist_ok=True)
with tempfile.TemporaryDirectory() as work:
col = Collection(os.path.join(work, "collection.anki2"))
col.media.write_data("cat.png", PNG)
col.media.write_data("neko.mp3", MP3)
japanese = col.decks.id("Japanese")
animals = col.decks.id("Japanese::Animals")
def add(model, deck, fields, tags):
note = col.new_note(col.models.by_name(model))
for i, field in enumerate(fields):
note.fields[i] = field
note.tags = tags
col.add_note(note, DeckId(deck))
return note
def answer(card_id, rating):
card = col.get_card(card_id)
card.start_timer()
states = col._backend.get_scheduling_states(card.id)
col.sched.answer_card(
col.sched.build_answer(card=card, states=states, rating=rating)
)
cat = add(
"Basic",
japanese,
["猫", 'cat
a pet
'],
["animal", "JLPT::N5"],
)
dog = add(
"Basic (and reversed card)",
animals,
["犬 [sound:neko.mp3]", "dog"],
["animal"],
)
add("Cloze", japanese, ["{{c1::Tokyo}} is the capital of {{c2::Japan::country}}", "extra"], [])
add(
"Basic",
animals,
["