summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/card.py3
-rw-r--r--src/cli.py0
-rw-r--r--src/flashcard_cli.py33
-rw-r--r--src/parser.py20
-rw-r--r--src/parser_unittest.py10
-rw-r--r--src/scheduler.py30
-rw-r--r--src/scheduler_brutal.py3
7 files changed, 84 insertions, 15 deletions
diff --git a/src/card.py b/src/card.py
index c4f7541..9f888e6 100644
--- a/src/card.py
+++ b/src/card.py
@@ -5,6 +5,9 @@ Defines a struct representing a single card. The struct takes the form:
"""
from collections import namedtuple
+from hashlib import md5
Card = namedtuple('Card', ['front', 'back'])
+def getId(card: Card) -> int:
+ return int(md5((card.front + card.back).encode("utf-8")).hexdigest(), 16)
diff --git a/src/cli.py b/src/cli.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/cli.py
diff --git a/src/flashcard_cli.py b/src/flashcard_cli.py
new file mode 100644
index 0000000..9e9633d
--- /dev/null
+++ b/src/flashcard_cli.py
@@ -0,0 +1,33 @@
+import click
+from json import load
+from os.path import isfile
+
+from card import Card
+from scheduler import getSchedulerClass, SCHEDULERS
+from parser import parseFiles
+
+
+@click.group()
+def cli():
+ pass
+
+@cli.command()
+@click.option("--scheduler", "scheduler_name", default="brutal", type=click.Choice(SCHEDULERS, case_sensitive=False))
+@click.argument("state_file", nargs=1, type=click.Path())
+@click.argument("card_files", nargs=-1, type=click.Path(exists=True))
+def practice(scheduler_name, state_file, card_files):
+ """
+ Run a practice session with the specified scheduler, using the provided state and card files.
+ """
+ cards = parseFiles(card_files)
+ state = json.load(state_file) if isfile(state_file) else {}
+
+ scheduler = getSchedulerClass(scheduler_name)(cards, state)
+
+
+
+
+
+if __name__ == '__main__':
+ cli()
+
diff --git a/src/parser.py b/src/parser.py
index d2b4ce7..ba7d247 100644
--- a/src/parser.py
+++ b/src/parser.py
@@ -30,13 +30,13 @@ id: card.Card
from pathlib import Path
from enum import Enum
from typing import TextIO, Iterator
-from card import Card
+from card import Card, getId
def _getCard(front_lines: list[str], back_lines: list[str]) -> tuple[int, Card]:
front_text = "".join(front_lines).strip()
back_text = "".join(back_lines).strip()
card = Card(front_text, back_text)
- id = hash(card)
+ id = getId(card)
return id, card
def _getCards(f: TextIO) -> Iterator[tuple[id, Card]]:
@@ -90,13 +90,19 @@ def _getCards(f: TextIO) -> Iterator[tuple[id, Card]]:
yield _getCard(front_lines, back_lines)
-def parse(path: Path) -> dict[int, Card]:
+def parseFile(path: str) -> dict[int, Card]:
"""
Parse a .fcard file and return a dictionary of Card instances indexed by ID.
"""
- if not path.is_file():
- print(f"[Warning] Not a file: {path}")
- return {}
-
with open(path, "r") as f:
return {id : card for id, card in _getCards(f)}
+
+def parseFiles(paths: list[str]) -> dict[int, Card]:
+ """
+ Parse a list of .fcard files and return a dictionary of Card instances indexed by ID.
+ """
+ cards = {}
+ for p in paths:
+ cards |= parseFile(p)
+ return cards
+
diff --git a/src/parser_unittest.py b/src/parser_unittest.py
index 9ada265..8d0d600 100644
--- a/src/parser_unittest.py
+++ b/src/parser_unittest.py
@@ -41,7 +41,7 @@ Another back
with open(path, "w") as f:
f.write(file_contents)
- cards = parser.parse(path)
+ cards = parser.parseFile(path)
assert expected == set(cards.values())
@@ -51,11 +51,7 @@ def test_emptyFile(tmp_path):
with open(path, "w") as f:
f.write("")
- cards = parser.parse(path)
- assert cards == {}
-
-def test_missingFile(tmp_path):
- cards = parser.parse(tmp_path / "missing_file.fcard")
+ cards = parser.parseFile(path)
assert cards == {}
def checkException(tmp_path, file_contents):
@@ -64,7 +60,7 @@ def checkException(tmp_path, file_contents):
f.write(file_contents)
with pytest.raises(Exception):
- cards = parser.parse(path)
+ cards = parser.parseFile(path)
def test_doesNotStartWithFront(tmp_path):
checkException(tmp_path, "BACK\noops")
diff --git a/src/scheduler.py b/src/scheduler.py
new file mode 100644
index 0000000..67e204a
--- /dev/null
+++ b/src/scheduler.py
@@ -0,0 +1,30 @@
+from typing import Protocol
+from abc import abstractmethod
+from card import Card
+
+class Scheduler(Protocol):
+ @abstractmethod
+ def __init__(self, cards: dict[int, Card], state: dict): raise NotImplementedError
+
+ @abstractmethod
+ def practice(self, size: int) -> list[int]: raise NotImplementedError
+
+ @abstractmethod
+ def test(self, size: int) -> list[int]: raise NotImplementedError
+
+ @abstractmethod
+ def update(self, results: dict[int, int]) -> None: raise NotImplementedError
+
+ @abstractmethod
+ def getState(self) -> dict: raise NotImplementedError
+
+SCHEDULERS = ["brutal"]
+
+def getSchedulerClass(name: str) -> Scheduler:
+ match name:
+ case "brutal":
+ from scheduler_brutal import SchedulerBrutal
+ return SchedulerBrutal
+ case _:
+ raise Exception(f"Unknown scheduler: {name}")
+
diff --git a/src/scheduler_brutal.py b/src/scheduler_brutal.py
index f41d203..e4e7ee2 100644
--- a/src/scheduler_brutal.py
+++ b/src/scheduler_brutal.py
@@ -1,12 +1,13 @@
"""
"""
+from scheduler import Scheduler
from card import Card
from random import shuffle
HISTORY_DEPTH = 8
-class SchedulerBrutal:
+class SchedulerBrutal(Scheduler):
def __init__(self, cards: dict[int, Card], state: dict):
self._cards = cards
self._state = {}