Skip to content

Commit

Permalink
Merge pull request #132 from MartinBernstorff/mb/rewrite_entrypoint
Browse files Browse the repository at this point in the history
refactor: rewrite entrypoint
  • Loading branch information
MartinBernstorff authored Sep 6, 2023
2 parents 36587bf + 02b1549 commit 8e68c36
Show file tree
Hide file tree
Showing 15 changed files with 181 additions and 90 deletions.
2 changes: 2 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,5 @@ updates:
commit-message:
prefix: "deps:"
include: "scope"
ignore:
- dependency-name: invoke
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@
"tests"
],
"python.testing.unittestEnabled": false,
"python.testing.pytestEnabled": true
"python.testing.pytestEnabled": true,
"python.analysis.typeCheckingMode": "strict"
}
24 changes: 12 additions & 12 deletions application/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,23 +20,22 @@
"""

import json
import os
import urllib.request
from collections import defaultdict
from pathlib import Path
from typing import Any, Dict

from docopt import docopt
from personal_mnemonic_medium.card_pipeline import CardPipeline
from personal_mnemonic_medium.exporters.anki.globals import (
CONFIG,
VERSION,
VERSION_LOG,
)
from personal_mnemonic_medium.exporters.anki.package_generator import (
AnkiPackageGenerator,
)
from personal_mnemonic_medium.exporters.anki.sync import sync_deck
from personal_mnemonic_medium.markdown_to_ankicard import markdown_to_ankicard
from personal_mnemonic_medium.note_factories.markdown import MarkdownNoteFactory
from personal_mnemonic_medium.prompt_extractors.cloze_extractor import (
ClozePromptExtractor,
)
Expand Down Expand Up @@ -103,13 +102,17 @@ def apply_arguments(arguments: Any) -> None:
def main():
"""Run the thing."""
apply_arguments(docopt(__doc__, version=VERSION))
initial_dir = Path(__file__).parent
recur_dir = Path(CONFIG["recur_dir"])
version_log = Path(CONFIG["version_log"])

cards = markdown_to_ankicard(
dir_path=recur_dir,
extractors=[QAPromptExtractor(), ClozePromptExtractor()],
cards = CardPipeline(
document_factory=MarkdownNoteFactory(), # Step 1, get the documents
prompt_extractors=[ # Step 2, get the prompts from the documents
QAPromptExtractor(),
ClozePromptExtractor(),
],
card_exporter=AnkiPackageGenerator(), # Step 3, get the cards from the prompts
).run(
input_path=recur_dir,
)

decks = defaultdict(list)
Expand All @@ -121,13 +124,10 @@ def main():
deck_bundle = AnkiPackageGenerator().cards_to_deck_bundle(cards=decks[deck])
sync_deck(
deck_bundle=deck_bundle,
dir_path=initial_dir,
dir_path=Path(__file__).parent,
max_wait_for_ankiconnect=30,
)

os.chdir(initial_dir)
json.dump(VERSION_LOG, Path(version_log).open("w"))


if __name__ == "__main__":
main()
4 changes: 3 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ dev = [
"ruff>=0.0.254",
"black[jupyter]>=22.8.0",
"pandas-stubs>=0.0.0", # type stubs for pandas
"invoke==2.2.0",
"invoke==2.1.0",
]
tests = [
"pytest>=7.1.3",
Expand Down Expand Up @@ -67,6 +67,8 @@ documentation = "https://MartinBernstorff.github.io/personal-mnemonic-medium/"
[tool.pyright]
exclude = [".*venv*", ".tox", "*.apkg"]
pythonPlatform = "Darwin"
typeCheckingMode = "basic"


[tool.ruff]
# Enable pycodestyle (`E`) and Pyflakes (`F`) codes by default.
Expand Down
47 changes: 47 additions & 0 deletions src/personal_mnemonic_medium/card_pipeline.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
from collections.abc import Sequence
from pathlib import Path
from typing import List

from personal_mnemonic_medium.exporters.anki.card_types.base import AnkiCard
from personal_mnemonic_medium.exporters.base import CardExporter
from personal_mnemonic_medium.note_factories.base import DocumentFactory
from personal_mnemonic_medium.note_factories.note import Document
from personal_mnemonic_medium.prompt_extractors.base import PromptExtractor
from personal_mnemonic_medium.prompt_extractors.prompt import Prompt


class CardPipeline:
def __init__(
self,
document_factory: DocumentFactory,
prompt_extractors: Sequence[PromptExtractor],
card_exporter: CardExporter,
) -> None:
self.document_factory = document_factory
self.prompt_extractors = prompt_extractors
self.card_exporter = card_exporter

def run(
self,
input_path: Path,
) -> List[AnkiCard]:
notes: List[Document] = []
if input_path.is_dir():
notes += list(self.document_factory.get_notes_from_dir(dir_path=input_path))

if not input_path.is_dir():
note_from_file = self.document_factory.get_note_from_file(
file_path=input_path,
)
notes.append(note_from_file)

collected_prompts: list[Prompt] = []

for extractor in self.prompt_extractors:
for note in notes:
collected_prompts += extractor.extract_prompts(note)

cards: list[AnkiCard] = self.card_exporter.prompts_to_cards(
prompts=collected_prompts,
)
return cards
14 changes: 9 additions & 5 deletions src/personal_mnemonic_medium/exporters/anki/package_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,16 @@
from dataclasses import dataclass
from pathlib import Path
from shutil import copyfile
from typing import Any, List, Set, Union
from typing import Any, List, Set

import genanki

from personal_mnemonic_medium.exporters.anki.card_types.base import AnkiCard
from personal_mnemonic_medium.exporters.anki.card_types.cloze import AnkiCloze
from personal_mnemonic_medium.exporters.anki.card_types.qa import AnkiQA
from personal_mnemonic_medium.exporters.base import CardExporter
from personal_mnemonic_medium.prompt_extractors.cloze_extractor import ClozePrompt
from personal_mnemonic_medium.prompt_extractors.prompt import Prompt
from personal_mnemonic_medium.prompt_extractors.qa_extractor import QAPrompt
from personal_mnemonic_medium.utils.hasher import simple_hash

Expand Down Expand Up @@ -52,7 +54,7 @@ def save_deck_to_file(self, output_path: Path) -> Path:
return output_path


class AnkiPackageGenerator:
class AnkiPackageGenerator(CardExporter):
"""Generates an anki package from a list of anki cards"""

def __init__(self) -> None:
Expand Down Expand Up @@ -100,11 +102,11 @@ def cards_to_deck(

def prompts_to_cards(
self,
prompts: Sequence[Union[QAPrompt, ClozePrompt]],
prompts: Sequence[Prompt],
) -> List[AnkiCard]:
"""Takes an iterable of prompts and turns them into AnkiCards"""

cards = []
cards: list[AnkiCard] = []

for prompt in prompts:
if isinstance(prompt, QAPrompt):
Expand All @@ -120,7 +122,9 @@ def prompts_to_cards(
fields=[prompt.content],
source_prompt=prompt,
)
else:
raise NotImplementedError(f"Prompt type {type(prompt)} not supported.")

cards += [card] # type: ignore
cards += [card]

return cards
11 changes: 11 additions & 0 deletions src/personal_mnemonic_medium/exporters/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from abc import ABC, abstractmethod
from collections.abc import Sequence

from personal_mnemonic_medium.exporters.anki.card_types.base import AnkiCard
from personal_mnemonic_medium.prompt_extractors.prompt import Prompt


class CardExporter(ABC):
@abstractmethod
def prompts_to_cards(self, prompts: Sequence[Prompt]) -> list[AnkiCard]:
pass
43 changes: 0 additions & 43 deletions src/personal_mnemonic_medium/markdown_to_ankicard.py

This file was deleted.

15 changes: 15 additions & 0 deletions src/personal_mnemonic_medium/note_factories/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from abc import ABC, abstractmethod
from collections.abc import Sequence
from pathlib import Path

from personal_mnemonic_medium.note_factories.note import Document


class DocumentFactory(ABC):
@abstractmethod
def get_notes_from_dir(self, dir_path: Path) -> Sequence[Document]:
pass

@abstractmethod
def get_note_from_file(self, file_path: Path) -> Document:
pass
8 changes: 5 additions & 3 deletions src/personal_mnemonic_medium/note_factories/markdown.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import os
import re
from collections.abc import Sequence
from pathlib import Path
from typing import List, Optional
from typing import Optional

from tqdm import tqdm

from personal_mnemonic_medium.note_factories.base import DocumentFactory
from personal_mnemonic_medium.note_factories.note import Document


class MarkdownNoteFactory:
class MarkdownNoteFactory(DocumentFactory):
def __init__(self, cut_note_after: str = "# Backlinks"):
"""Create a new MarkdownNoteFactory.
Expand Down Expand Up @@ -50,7 +52,7 @@ def get_note_from_file(self, file_path: Path) -> Optional[Document]:
source_path=file_path,
)

def get_notes_from_dir(self, dir_path: Path) -> List[Document]:
def get_notes_from_dir(self, dir_path: Path) -> Sequence[Document]:
notes: list[Document] = []

for parent_dir, _, files in os.walk(dir_path):
Expand Down
11 changes: 11 additions & 0 deletions src/personal_mnemonic_medium/prompt_extractors/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from abc import ABC, abstractmethod
from collections.abc import Sequence

from personal_mnemonic_medium.note_factories.note import Document
from personal_mnemonic_medium.prompt_extractors.prompt import Prompt


class PromptExtractor(ABC):
@abstractmethod
def extract_prompts(self, note: Document) -> Sequence[Prompt]:
pass
18 changes: 10 additions & 8 deletions src/personal_mnemonic_medium/prompt_extractors/cloze_extractor.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import hashlib
import re
from collections.abc import Sequence
from typing import Any, List, Optional

from personal_mnemonic_medium.note_factories.note import Document
from personal_mnemonic_medium.prompt_extractors.base import PromptExtractor
from personal_mnemonic_medium.prompt_extractors.prompt import Prompt


Expand All @@ -12,17 +14,17 @@ def __init__(self, content: str, *args: Any, **kwargs: Any):
self.content = content


class ClozePromptExtractor:
class ClozePromptExtractor(PromptExtractor):
def __init__(self) -> None:
pass

@staticmethod
def break_string_by_two_or_more_newlines(string: str) -> List[str]:
def _break_string_by_two_or_more_newlines(string: str) -> List[str]:
"""Break string into a list by 2+ newlines in a row."""
return re.split(r"(\n\n)+", string)

@staticmethod
def has_cloze(string: str) -> bool:
def _has_cloze(string: str) -> bool:
if (
len(re.findall(r"{.*}", string)) > 0
and "BearID" not in string # Exclude BearID
Expand All @@ -35,7 +37,7 @@ def has_cloze(string: str) -> bool:
return False

@staticmethod
def replace_cloze_id_with_unique(
def _replace_cloze_id_with_unique(
string: str,
selected_cloze: Optional[str] = None,
) -> str:
Expand Down Expand Up @@ -63,17 +65,17 @@ def replace_cloze_id_with_unique(

return string

def extract_prompts(self, note: Document) -> List[ClozePrompt]:
def extract_prompts(self, note: Document) -> Sequence[ClozePrompt]:
prompts = []

blocks = self.break_string_by_two_or_more_newlines(note.content)
blocks = self._break_string_by_two_or_more_newlines(note.content)

for block_string in blocks:
if self.has_cloze(block_string):
if self._has_cloze(block_string):
clozes = re.findall(r"{(?!BearID).[^}]*}", block_string)

for selected_cloze in clozes:
prompt_content = self.replace_cloze_id_with_unique(
prompt_content = self._replace_cloze_id_with_unique(
block_string,
selected_cloze=selected_cloze,
)
Expand Down
Loading

0 comments on commit 8e68c36

Please sign in to comment.