-
Notifications
You must be signed in to change notification settings - Fork 2
/
book_show_types.py
181 lines (135 loc) · 5.63 KB
/
book_show_types.py
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
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
#!/usr/bin/env python3
"""
Game of Thrones chapters vs episodes chart generator
Copyright (c) 2013-2018, Joel Geddert
This script generates an HTML file of the table.
Software License:
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
A note from the author:
The original chart generated by this code, as well as all remaining applicable
source & asset files (except where noted), are licensed under a Creative Commons
BY-SA 4.0 license <http://creativecommons.org/licenses/by-sa/4.0/>. If you are
going to use any of this code to create a derivative work, please respect this
CC license.
"""
from typing import List
from utils import find_unique
from dataclasses import dataclass, field
# Technically it's not 100% correct to set frozen=True, as some of these members (the lists) are mutable and we will be
# modifiing them later. I'm not sure if that's non-pythonic, but at least it protects against changing the other fields
@dataclass(frozen=True)
class Book:
number: int
name: str
abbreviation: str
chapters: List = field(default_factory=list)
combined_books: List = field(default_factory=list)
def is_combined(self):
return bool(self.combined_books)
def __str__(self):
return self.name
def __repr__(self):
if self.is_combined():
return 'Book(%i (%s): %s ("%s"), %i chapters)' % (
self.number,
'+'.join(['%i' % book.number for book in self.combined_books]),
self.name,
self.abbreviation,
len(self.chapters),
)
else:
return 'Book(%i: %s ("%s"), %i chapters)' % (
self.number,
self.name,
self.abbreviation,
len(self.chapters),
)
@dataclass(frozen=True)
class Chapter:
number: int
book: Book
number_in_book: int
name: str
pov: str
occurred: bool
def __str__(self):
return 'Chapter %i: "%s"' % (self.number, self.name)
def __repr__(self):
return 'Chapter(%i, %s %i: %s, POV %s, occurred: %s)' % (
self.number, self.book.name, self.number_in_book, self.name, self.pov, str(self.occurred))
@dataclass(frozen=True)
class Season:
number: int
episodes: List = field(default_factory=list)
def __str__(self):
return 'Season %i' % self.number
def __repr__(self):
return 'Season(%i, %i episodes)' % (self.number, len(self.episodes))
@dataclass(frozen=True)
class Episode:
number: int
number_in_season: int
season: Season
name: str
book_connections: List = field(default_factory=list)
def __str__(self):
return '%i (%ix%02i) "%s"' % (self.number, self.season.number, self.number_in_season, self.name)
def __repr__(self):
return 'Episode(%s, %i book connections)' % (str(self), len(self.book_connections))
@dataclass(frozen=True)
class Connection:
episode: Episode
chapter: Chapter
strength: int
major: bool
notes: str
def __str__(self):
return 'Episode %i <-> Chapter %i' % (self.episode.number, self.chapter.number)
def __repr__(self):
return 'Connection(Episode %i, Chapter %i, Strength %s, Major %s, Notes: %s)' % (
self.episode.number, self.chapter.number, str(self.strength), str(self.major), self.notes)
class DB:
def __init__(self):
self.books = []
self.seasons = []
def find_chapter(self, chap_name, book_num):
book = find_unique(self.books, lambda book: book.number == book_num)
return find_unique(book.chapters, lambda chapter: chapter.name == chap_name)
# There is some duplicate data in here for convenience sake. For example:
# * Chapter doesn't need reference back to book, since that could be determined from book list
# * Don't need chapter.number_in_book, since that could be deduced from position in book.chapters
# * Don't need chapter.number, since that could then further be deduced from book's position in db.books and number
# of chapters in each book
#
# These sanity checks ensure this duplicate data is all correct
def sanity_check(self):
# Books & chapters
real_books = [book for book in self.books if not book.is_combined()]
if not all([book.number == idx + 1 for idx, book in enumerate(real_books)]):
raise ValueError('Book list is not sorted & complete')
for book in real_books:
if not all([chapter.number_in_book == idx + 1 for idx, chapter in enumerate(book.chapters)]):
raise ValueError('Chapter number in book does not match position in book list')
if not all([chapter.book is book for chapter in book.chapters]):
raise ValueError("Chapter's book reference does not match book it is in!")
# Seasons & episodes
if not all([season.number == idx + 1 for idx, season in enumerate(self.seasons)]):
raise ValueError('Season list is not sorted & complete')
for season in self.seasons:
if not all([episode.number_in_season == idx + 1 for idx, episode in enumerate(season.episodes)]):
raise ValueError('Episode number in season does not match position in season list')
if not all([episode.season is season for episode in season.episodes]):
raise ValueError("Episodes's season reference does not match season it is in!")
for episode in season.episodes:
for connection in episode.book_connections:
if not connection.episode is episode:
raise ValueError("Connection's episode reference does not match episode it is in!")