diff --git a/pyproject.toml b/pyproject.toml index 9317a58..ef0f3d9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,6 +14,7 @@ requires-python = ">=3.9.20" dependencies = [ "aiohttp>=3.11.11", "tree-sitter-language-pack>=0.2.0", + "typer>=0.15.1", ] [project.scripts] diff --git a/src/anki/anki_api.py b/src/anki/anki_api.py index 82c4e1e..81bc14f 100644 --- a/src/anki/anki_api.py +++ b/src/anki/anki_api.py @@ -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): diff --git a/src/anki/flashcard.py b/src/anki/flashcard.py index d487b63..ccfeafe 100644 --- a/src/anki/flashcard.py +++ b/src/anki/flashcard.py @@ -34,7 +34,7 @@ class Flashcard: def as_html(self, front: bool) -> str: prefix = f"" # indexable via anki search - image = f'' + image = f'' return prefix + image def as_anki_model(self, tmp: bool = False) -> dict: diff --git a/src/anki/main.py b/src/anki/main.py index 12af433..c29e50b 100644 --- a/src/anki/main.py +++ b/src/anki/main.py @@ -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__": diff --git a/src/anki/typst_compiler.py b/src/anki/typst_compiler.py index 2467a40..189a106 100644 --- a/src/anki/typst_compiler.py +++ b/src/anki/typst_compiler.py @@ -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): diff --git a/uv.lock b/uv.lock index 4cee82c..3dda66a 100644 --- a/uv.lock +++ b/uv.lock @@ -133,6 +133,27 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/89/aa/ab0f7891a01eeb2d2e338ae8fecbe57fcebea1a24dbb64d45801bfab481d/attrs-24.3.0-py3-none-any.whl", hash = "sha256:ac96cd038792094f438ad1f6ff80837353805ac950cd2aa0e0625ef19850c308", size = 63397 }, ] +[[package]] +name = "click" +version = "8.1.8" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "platform_system == 'Windows'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188 }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, +] + [[package]] name = "frozenlist" version = "1.5.0" @@ -226,6 +247,27 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 }, ] +[[package]] +name = "markdown-it-py" +version = "3.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mdurl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528 }, +] + +[[package]] +name = "mdurl" +version = "0.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979 }, +] + [[package]] name = "multidict" version = "6.1.0" @@ -402,6 +444,38 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/41/b6/c5319caea262f4821995dca2107483b94a3345d4607ad797c76cb9c36bcc/propcache-0.2.1-py3-none-any.whl", hash = "sha256:52277518d6aae65536e9cea52d4e7fd2f7a66f4aa2d30ed3f2fcea620ace3c54", size = 11818 }, ] +[[package]] +name = "pygments" +version = "2.18.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8e/62/8336eff65bcbc8e4cb5d05b55faf041285951b6e80f33e2bff2024788f31/pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199", size = 4891905 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f7/3f/01c8b82017c199075f8f788d0d906b9ffbbc5a47dc9918a945e13d5a2bda/pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a", size = 1205513 }, +] + +[[package]] +name = "rich" +version = "13.9.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, + { name = "pygments" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ab/3a/0316b28d0761c6734d6bc14e770d85506c986c85ffb239e688eeaab2c2bc/rich-13.9.4.tar.gz", hash = "sha256:439594978a49a09530cff7ebc4b5c7103ef57baf48d5ea3184f21d9a2befa098", size = 223149 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/19/71/39c7c0d87f8d4e6c020a393182060eaefeeae6c01dab6a84ec346f2567df/rich-13.9.4-py3-none-any.whl", hash = "sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90", size = 242424 }, +] + +[[package]] +name = "shellingham" +version = "1.5.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755 }, +] + [[package]] name = "tree-sitter" version = "0.23.2" @@ -555,6 +629,21 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2e/32/af2d676b0176a958f22a75b04be836e09476a10844baab78c018a5030297/tree_sitter_yaml-0.7.0-cp39-abi3-win_arm64.whl", hash = "sha256:f0f8d8e05fa8e70f08d0f18a209d6026e171844f4ea7090e7c779b9c375b3a31", size = 43650 }, ] +[[package]] +name = "typer" +version = "0.15.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "rich" }, + { name = "shellingham" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cb/ce/dca7b219718afd37a0068f4f2530a727c2b74a8b6e8e0c0080a4c0de4fcd/typer-0.15.1.tar.gz", hash = "sha256:a0588c0a7fa68a1978a069818657778f86abe6ff5ea6abf472f940a08bfe4f0a", size = 99789 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d0/cc/0a838ba5ca64dc832aa43f727bd586309846b0ffb2ce52422543e6075e8a/typer-0.15.1-py3-none-any.whl", hash = "sha256:7994fb7b8155b64d3402518560648446072864beefd44aa2dc36972a5972e847", size = 44908 }, +] + [[package]] name = "typing-extensions" version = "4.12.2" @@ -571,12 +660,14 @@ source = { editable = "." } dependencies = [ { name = "aiohttp" }, { name = "tree-sitter-language-pack" }, + { name = "typer" }, ] [package.metadata] requires-dist = [ { name = "aiohttp", specifier = ">=3.11.11" }, { name = "tree-sitter-language-pack", specifier = ">=0.2.0" }, + { name = "typer", specifier = ">=0.15.1" }, ] [[package]]