feat(anki): basic cli using typer

This commit is contained in:
arne314
2024-12-22 22:53:10 +01:00
parent ac353e5c74
commit 1124181f59
6 changed files with 120 additions and 17 deletions

View File

@@ -26,6 +26,7 @@ class AnkiConnectApi:
add.append(card)
else:
update.append(card)
print(f"Pushing {len(add)} new flashcards and {len(update)} updated flashcards to Anki...")
await asyncio.gather(self._add(add), self._update(update))
async def _request_api(self, action, **params):

View File

@@ -34,7 +34,7 @@ class Flashcard:
def as_html(self, front: bool) -> str:
prefix = f"<p hidden>{self.front}: {self.back}{' ' * 10}</p>" # indexable via anki search
image = f'<img src="{self.svg_filename(front)}" width=100 />'
image = f'<img src="{self.svg_filename(front)}" />'
return prefix + image
def as_anki_model(self, tmp: bool = False) -> dict:

View File

@@ -1,34 +1,37 @@
import asyncio
import glob
import os
from typing_extensions import Annotated
import typer
from anki.anki_api import AnkiConnectApi
from anki.file_handler import FileHandler
from anki.parser import FlashcardParser
from anki.typst_compiler import TypstCompiler
parser = FlashcardParser()
compiler = TypstCompiler(os.getcwd())
api = AnkiConnectApi()
cli = typer.Typer(name="typstar-anki")
async def export_flashcards(path):
async def export_flashcards(root_dir, typst_cmd):
parser = FlashcardParser()
compiler = TypstCompiler(root_dir, typst_cmd)
api = AnkiConnectApi()
# parse flashcards
print("Parsing flashcards...")
flashcards = []
file_handlers = []
for file in glob.glob(f"{path}/**/*.typ", recursive=True):
for file in glob.glob(f"{root_dir}/**/*.typ", recursive=True):
fh = FileHandler(file)
cards = parser.parse_file(fh)
file_handlers.append((fh, cards))
flashcards.extend(cards)
# async typst compilation
print("Compiling flashcards...")
await compiler.compile_flashcards(flashcards)
# async anki push per deck
print("Pushing flashcards to anki...")
await api.push_flashcards(flashcards)
# write id updates to files
@@ -44,8 +47,15 @@ async def export_flashcards(path):
print("Done")
@cli.command()
def cmd(root_dir: Annotated[
str, typer.Option(help="Directory scanned for flashcards and passed over to typst compile command")] = os.getcwd(),
typst_cmd: Annotated[str, typer.Option(help="Typst command used for flashcard compilation")] = "typst"):
asyncio.run(export_flashcards(root_dir, typst_cmd))
def main():
asyncio.run(export_flashcards(os.getcwd()))
typer.run(cmd)
if __name__ == "__main__":

View File

@@ -5,7 +5,8 @@ from typing import List
from .flashcard import Flashcard
default_preamble = """
#set page(width: auto, height: auto, margin: (rest: 0%))
#set text(size: 20pt)
#set page(width: auto, height: auto, margin: (rest: 8pt))
#let flashcard(id, front, back) = {
strong(front)
[\\ ]
@@ -24,7 +25,7 @@ class TypstCompiler:
typst_root_dir: str
max_processes: int
def __init__(self, typst_root_dir: str = ".", typst_cmd: str = "typst", preamble: str = None):
def __init__(self, typst_root_dir: str, typst_cmd: str, preamble: str = None):
if preamble is None:
preamble = default_preamble
self.typst_cmd = typst_cmd
@@ -39,12 +40,10 @@ class TypstCompiler:
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
)
proc.stdin.write(bytes(src, encoding="utf-8"))
proc.stdin.close()
await proc.wait()
if err := await proc.stderr.read():
raise TypstCompilationError(bytes.decode(err, encoding="utf-8"))
return await proc.stdout.read()
stdout, stderr = await proc.communicate(bytes(src, encoding="utf-8"))
if stderr:
raise TypstCompilationError(bytes.decode(stderr, encoding="utf-8"))
return stdout
async def _compile_flashcard(self, card: Flashcard):
front = await self._compile(self.preamble + "\n" + card.as_typst(True))
@@ -52,6 +51,7 @@ class TypstCompiler:
card.set_svgs(front, back)
async def compile_flashcards(self, cards: List[Flashcard]):
print(f"Compiling {len(cards)} flashcards...")
semaphore = asyncio.Semaphore(self.max_processes)
async def compile_coro(card):