mirror of
https://github.com/Ascyii/typstar.git
synced 2026-01-01 13:34:24 -05:00
feat(anki): default deck per directory
This commit is contained in:
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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
35
src/anki/config_parser.py
Normal 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
|
||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user