feat(anki): default deck per directory

This commit is contained in:
arne314
2025-01-10 16:38:34 +01:00
parent a1f2276614
commit 1de9ecee92
5 changed files with 48 additions and 22 deletions

View File

@@ -57,6 +57,7 @@ To render the flashcard in your document as well add some code like this
``` ```
- Add a comment like `// ANKI: MY::DECK` to your document to set a deck used for all flashcards after this comment (You can use multiple decks per file) - Add a comment like `// ANKI: MY::DECK` to your document to set a deck used for all flashcards after this comment (You can use multiple decks per file)
- Add a file named `.anki` containing a deck name to define a default deck on a directory base
- Add a file named `.anki.typ` to define a preamble on a directory base. You can find the default preamble [here](./src/anki/typst_compiler.py). - Add a file named `.anki.typ` to define a preamble on a directory base. You can find the default preamble [here](./src/anki/typst_compiler.py).
- Tip: Despite the use of SVGs you can still search your flashcards in Anki as the typst source is added into an invisible html paragraph - Tip: Despite the use of SVGs you can still search your flashcards in Anki as the typst source is added into an invisible html paragraph

View File

@@ -4,7 +4,7 @@ build-backend = "pdm.backend"
[project] [project]
name = "typstar" name = "typstar"
version = "1.1.1" version = "1.2.0"
description = "Neovim plugin for efficient note taking in Typst" description = "Neovim plugin for efficient note taking in Typst"
authors = [ authors = [
{ name = "arne314" } { name = "arne314" }

35
src/anki/config_parser.py Normal file
View File

@@ -0,0 +1,35 @@
from collections import defaultdict
from functools import cache
from glob import glob
from pathlib import Path
class RecursiveConfigParser:
dir: Path
targets: set[str]
results: dict[str, dict[Path, str]]
def __init__(self, dir, targets):
self.dir = dir
self.targets = set(targets)
self.results = defaultdict(dict)
self._parse_recursive()
def _parse_recursive(self):
files = []
for target in self.targets:
files.extend(glob(f"{self.dir}/**/{target}", include_hidden=target.startswith("."), recursive=True))
for file in files:
file = Path(file)
if file.name in self.targets:
self.results[file.name][file.parent] = file.read_text(encoding="utf-8")
@cache
def get_config(self, path: Path, target) -> str | None:
root_parent = self.dir.parent.resolve()
path = Path(path.resolve())
target_results = self.results[target]
while path != root_parent:
if result := target_results.get(path):
return result
path = path.parent

View File

@@ -1,7 +1,6 @@
import glob
import json import json
import re import re
from functools import cache from glob import glob
from pathlib import Path from pathlib import Path
from typing import List, Tuple from typing import List, Tuple
@@ -9,6 +8,7 @@ import appdirs
import tree_sitter import tree_sitter
from tree_sitter_typst import language as get_typst_language from tree_sitter_typst import language as get_typst_language
from .config_parser import RecursiveConfigParser
from .file_handler import FileHandler from .file_handler import FileHandler
from .flashcard import Flashcard from .flashcard import Flashcard
@@ -38,6 +38,8 @@ ts_deck_query = """
deck_regex = re.compile(r"\W+ANKI:\s*([\S ]*)") deck_regex = re.compile(r"\W+ANKI:\s*([\S ]*)")
class FlashcardParser: class FlashcardParser:
typst_language: tree_sitter.Language typst_language: tree_sitter.Language
typst_parser: tree_sitter.Parser typst_parser: tree_sitter.Parser
@@ -56,7 +58,7 @@ class FlashcardParser:
self.file_handlers = [] self.file_handlers = []
self._load_file_hashes() self._load_file_hashes()
def _parse_file(self, file: FileHandler, preamble: str | None) -> List[Flashcard]: def _parse_file(self, file: FileHandler, preamble: str | None, default_deck: str | None) -> List[Flashcard]:
cards = [] cards = []
tree = self.typst_parser.parse(file.get_bytes(), encoding="utf8") tree = self.typst_parser.parse(file.get_bytes(), encoding="utf8")
card_captures = self.flashcard_query.captures(tree.root_node) card_captures = self.flashcard_query.captures(tree.root_node)
@@ -73,7 +75,7 @@ class FlashcardParser:
deck_refs: List[Tuple[int, str | None]] = [] deck_refs: List[Tuple[int, str | None]] = []
deck_refs_idx = -1 deck_refs_idx = -1
current_deck = None current_deck = default_deck
if deck_captures: if deck_captures:
deck_captures["deck"].sort(key=row_compare) deck_captures["deck"].sort(key=row_compare)
for comment in deck_captures["deck"]: for comment in deck_captures["deck"]:
@@ -108,6 +110,7 @@ class FlashcardParser:
return cards return cards
def parse_directory(self, root_dir: Path, force_scan: Path | None = None): def parse_directory(self, root_dir: Path, force_scan: Path | None = None):
flashcards = []
single_file = None single_file = None
is_force_scan = force_scan is not None is_force_scan = force_scan is not None
if is_force_scan: if is_force_scan:
@@ -123,22 +126,9 @@ class FlashcardParser:
f"Parsing flashcards in {scan_dir if single_file is None else single_file} ...", f"Parsing flashcards in {scan_dir if single_file is None else single_file} ...",
flush=True, flush=True,
) )
preambles = {} configs = RecursiveConfigParser(root_dir, {".anki", ".anki.typ"})
flashcards = []
@cache for file in glob(f"{scan_dir}/**/**.typ", recursive=True):
def get_preamble(path: Path) -> str | None:
while path != root_dir.parent:
if preamble := preambles.get(path):
return preamble
path = path.parent
for file in glob.glob(f"{root_dir}/**/.anki.typ", include_hidden=True, recursive=True):
file = Path(file)
if file.name == ".anki.typ":
preambles[file.parent] = file.read_text(encoding="utf-8")
for file in glob.glob(f"{scan_dir}/**/**.typ", recursive=True):
file = Path(file) file = Path(file)
if single_file is not None and file != single_file: if single_file is not None and file != single_file:
continue continue
@@ -146,7 +136,7 @@ class FlashcardParser:
fh = FileHandler(file) fh = FileHandler(file)
file_changed = self._hash_changed(fh) file_changed = self._hash_changed(fh)
if is_force_scan or file_changed: if is_force_scan or file_changed:
cards = self._parse_file(fh, get_preamble(file.parent)) cards = self._parse_file(fh, configs.get_config(file, ".anki.typ"), configs.get_config(file, ".anki"))
self.file_handlers.append((fh, cards)) self.file_handlers.append((fh, cards))
flashcards.extend(cards) flashcards.extend(cards)
return flashcards return flashcards

2
uv.lock generated
View File

@@ -437,7 +437,7 @@ wheels = [
[[package]] [[package]]
name = "typstar" name = "typstar"
version = "1.1.0" version = "1.2.0"
source = { editable = "." } source = { editable = "." }
dependencies = [ dependencies = [
{ name = "aiohttp" }, { name = "aiohttp" },