MIL-003: Step 2 - New flash cards
Review record RC-025 (Go, autonomous mode, not independent). Task: MIL-003#1 Task: MIL-003#2 Task: MIL-003#3 Task: MIL-003#4 Task: MIL-003#5
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
"""Domain entities.
|
||||
|
||||
@file __init__.py
|
||||
@brief Holds the entities of the flash card program.
|
||||
"""
|
||||
@@ -0,0 +1,17 @@
|
||||
"""A flash card.
|
||||
|
||||
@file card.py
|
||||
@brief The Card entity: a word and its translation.
|
||||
"""
|
||||
|
||||
from dataclasses import dataclass
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class Card:
|
||||
"""A word and its translation."""
|
||||
|
||||
## @brief The word in the front language.
|
||||
front: str
|
||||
## @brief The translation in the back language.
|
||||
back: str
|
||||
@@ -0,0 +1,95 @@
|
||||
"""The cards still to learn.
|
||||
|
||||
@file deck.py
|
||||
@brief The Deck entity: holds cards, draws one and removes one.
|
||||
"""
|
||||
|
||||
from collections.abc import Callable, Iterable, Sequence
|
||||
|
||||
from flash_card.domain.entities.card import Card
|
||||
from flash_card.domain.entities.vocabulary import Vocabulary
|
||||
from flash_card.domain.errors.vocabulary_errors import EmptyDeckError
|
||||
from flash_card.domain.value_objects.language_pair import LanguagePair
|
||||
|
||||
|
||||
class Deck:
|
||||
"""The cards still to learn, of one language pair."""
|
||||
|
||||
def __init__(self, language_pair: LanguagePair, cards: Iterable[Card]) -> None:
|
||||
"""Create a deck.
|
||||
|
||||
@param language_pair The languages of the cards.
|
||||
@param cards The cards of the deck.
|
||||
"""
|
||||
## @brief The languages of the cards.
|
||||
self._language_pair = language_pair
|
||||
## @brief The cards still in the deck.
|
||||
self._cards = list(cards)
|
||||
|
||||
@classmethod
|
||||
def from_vocabulary(cls, vocabulary: Vocabulary) -> "Deck":
|
||||
"""Create a deck holding every card of a vocabulary.
|
||||
|
||||
@param vocabulary The delivered word list.
|
||||
@return A new deck.
|
||||
"""
|
||||
return cls(vocabulary.language_pair, vocabulary.cards)
|
||||
|
||||
@property
|
||||
def language_pair(self) -> LanguagePair:
|
||||
"""Return the languages of the cards.
|
||||
|
||||
@return The language pair.
|
||||
"""
|
||||
return self._language_pair
|
||||
|
||||
@property
|
||||
def cards(self) -> tuple[Card, ...]:
|
||||
"""Return a copy of the cards.
|
||||
|
||||
@return The cards in deck order.
|
||||
"""
|
||||
return tuple(self._cards)
|
||||
|
||||
@property
|
||||
def is_empty(self) -> bool:
|
||||
"""Tell whether the deck has no card.
|
||||
|
||||
@return True when no card is left.
|
||||
"""
|
||||
return not self._cards
|
||||
|
||||
def __len__(self) -> int:
|
||||
"""Return the number of cards.
|
||||
|
||||
@return The card count.
|
||||
"""
|
||||
return len(self._cards)
|
||||
|
||||
def draw(
|
||||
self,
|
||||
choose: Callable[[Sequence[Card]], Card],
|
||||
exclude: Card | None = None,
|
||||
) -> Card:
|
||||
"""Pick a card without removing it.
|
||||
|
||||
The pick avoids the excluded card when another card exists.
|
||||
|
||||
@param choose Picks one card from a non-empty sequence.
|
||||
@param exclude A card to avoid, usually the one shown now.
|
||||
@return The picked card.
|
||||
@throws EmptyDeckError When the deck has no card.
|
||||
"""
|
||||
if not self._cards:
|
||||
raise EmptyDeckError("cannot draw from an empty deck")
|
||||
others = [card for card in self._cards if card != exclude]
|
||||
return choose(others or self._cards)
|
||||
|
||||
def remove(self, card: Card) -> None:
|
||||
"""Remove the first card equal to the given one.
|
||||
|
||||
@param card The card to remove.
|
||||
@return Nothing.
|
||||
@throws ValueError When the deck holds no such card.
|
||||
"""
|
||||
self._cards.remove(card)
|
||||
@@ -0,0 +1,20 @@
|
||||
"""The delivered word list.
|
||||
|
||||
@file vocabulary.py
|
||||
@brief The Vocabulary entity: all cards of a language pair.
|
||||
"""
|
||||
|
||||
from dataclasses import dataclass
|
||||
|
||||
from flash_card.domain.entities.card import Card
|
||||
from flash_card.domain.value_objects.language_pair import LanguagePair
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class Vocabulary:
|
||||
"""All cards of a language pair as delivered."""
|
||||
|
||||
## @brief The languages of the cards.
|
||||
language_pair: LanguagePair
|
||||
## @brief The cards in file order.
|
||||
cards: tuple[Card, ...]
|
||||
@@ -0,0 +1,5 @@
|
||||
"""Domain errors.
|
||||
|
||||
@file __init__.py
|
||||
@brief Holds the errors the domain rules raise.
|
||||
"""
|
||||
@@ -0,0 +1,21 @@
|
||||
"""Errors of the vocabulary and the deck.
|
||||
|
||||
@file vocabulary_errors.py
|
||||
@brief The VocabularyError hierarchy.
|
||||
"""
|
||||
|
||||
|
||||
class VocabularyError(Exception):
|
||||
"""Base of every error about the word list or the deck."""
|
||||
|
||||
|
||||
class VocabularyFormatError(VocabularyError):
|
||||
"""The word list is not in the expected format."""
|
||||
|
||||
|
||||
class VocabularyNotFoundError(VocabularyError):
|
||||
"""The word list does not exist."""
|
||||
|
||||
|
||||
class EmptyDeckError(VocabularyError):
|
||||
"""A card was drawn from a deck with no card."""
|
||||
@@ -0,0 +1,5 @@
|
||||
"""Domain repository ports.
|
||||
|
||||
@file __init__.py
|
||||
@brief Holds the repository interfaces of the domain.
|
||||
"""
|
||||
@@ -0,0 +1,21 @@
|
||||
"""Port to the delivered word list.
|
||||
|
||||
@file vocabulary_repository.py
|
||||
@brief The VocabularyRepository interface.
|
||||
"""
|
||||
|
||||
from typing import Protocol
|
||||
|
||||
from flash_card.domain.entities.vocabulary import Vocabulary
|
||||
|
||||
|
||||
class VocabularyRepository(Protocol):
|
||||
"""Source of the delivered word list."""
|
||||
|
||||
def load(self) -> Vocabulary:
|
||||
"""Load the delivered word list.
|
||||
|
||||
@return The vocabulary.
|
||||
@throws VocabularyError When the word list cannot be loaded.
|
||||
"""
|
||||
...
|
||||
@@ -0,0 +1,5 @@
|
||||
"""Domain value objects.
|
||||
|
||||
@file __init__.py
|
||||
@brief Holds the immutable value objects of the domain.
|
||||
"""
|
||||
@@ -0,0 +1,17 @@
|
||||
"""The two languages of a word list.
|
||||
|
||||
@file language_pair.py
|
||||
@brief The LanguagePair value object.
|
||||
"""
|
||||
|
||||
from dataclasses import dataclass
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class LanguagePair:
|
||||
"""The language codes of the front and the back of the cards."""
|
||||
|
||||
## @brief Language code of the card front.
|
||||
front_code: str
|
||||
## @brief Language code of the card back.
|
||||
back_code: str
|
||||
Reference in New Issue
Block a user