1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
|
"""
Load .fcard files into dictionaries.
The parser expects .fcard files in the following format:
FRONT
This is the front of the first card.
BACK
This is the back of the first card.
FRONT
This is another card.
Multiple lines on the front are allowed.
BACK
Multiple lines on the back?
Also allowed.
FRONT
...
The cards are represented in dictionary entries of the form:
id: card.Card
"""
from pathlib import Path
from enum import Enum
from typing import TextIO, Iterator
from .card import Card, getId
def _getCard(front_lines: list[str], back_lines: list[str]) -> tuple[str, Card]:
front_text = "".join(front_lines).strip()
back_text = "".join(back_lines).strip()
card = Card(front_text, back_text)
id = getId(card)
return id, card
def _getCards(f: TextIO) -> Iterator[tuple[str, Card]]:
class State(Enum):
PARSE_FRONT = 1,
PARSE_BACK = 2
state = None
front_lines = []
back_lines = []
for i, line in enumerate(f):
match line.strip():
case "FRONT":
# Edge case: FRONT twice in a row
if state == State.PARSE_FRONT:
raise Exception(f"Unexpected 'FRONT': {f}:{i}")
# Next card is starting, wrap up current one
if state == State.PARSE_BACK:
yield _getCard(front_lines, back_lines)
front_lines.clear()
back_lines.clear()
state = State.PARSE_FRONT
case "BACK":
# Edge case: BACK without FRONT before it
if state != State.PARSE_FRONT:
raise Exception(f"Unexpected 'BACK': {f}:{i}")
state = State.PARSE_BACK
case _:
match state:
case State.PARSE_FRONT:
front_lines += line
case State.PARSE_BACK:
back_lines += line
# Edge case: file does not start with FRONT, flush preamble
case _:
continue
# Edge case: file did not end with contents of BACK
if state == State.PARSE_FRONT:
raise Exception(f"Unexpected end of file")
# Edge case: file was empty
if state is None:
return
yield _getCard(front_lines, back_lines)
def parseFile(path: str) -> dict[str, Card]:
"""
Parse a .fcard file and return a dictionary of Card instances indexed by ID.
"""
with open(path, "r") as f:
return {id : card for id, card in _getCards(f)}
def parseFiles(paths: list[str]) -> dict[str, 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
|