diff options
| author | Eddy Pedroni <epedroni@pm.me> | 2024-09-25 16:12:53 +0200 | 
|---|---|---|
| committer | Eddy Pedroni <epedroni@pm.me> | 2024-09-25 16:12:53 +0200 | 
| commit | 28afae563e3e32187b389db4680bcc08b9fc55a9 (patch) | |
| tree | 0cb940560297401924316f3fc7226bd7983eb2ad /src | |
| parent | fe36d1261dc96004e4d4e692a65e1bc53137726b (diff) | |
General improvements, CLI initial implementation
Diffstat (limited to 'src')
| -rw-r--r-- | src/card.py | 3 | ||||
| -rw-r--r-- | src/cli.py | 0 | ||||
| -rw-r--r-- | src/flashcard_cli.py | 33 | ||||
| -rw-r--r-- | src/parser.py | 20 | ||||
| -rw-r--r-- | src/parser_unittest.py | 10 | ||||
| -rw-r--r-- | src/scheduler.py | 30 | ||||
| -rw-r--r-- | src/scheduler_brutal.py | 3 | 
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 = {} | 
