Add an entry for "Solutions and Other Problems"
[books.alexwlchan.net] / scripts / add_book.py
1 #!/usr/bin/env python3
2
3 import datetime
4 import os
5 import re
6 import subprocess
7 from urllib.request import urlretrieve
8
9 import frontmatter
10 import hyperlink
11 import inquirer
12 from unidecode import unidecode
13
14
15 def slugify(u):
16     """Convert Unicode string into blog slug."""
17     # https://leancrew.com/all-this/2014/10/asciifying/
18     u = re.sub("[–—/:;,.]", "-", u)  # replace separating punctuation
19     a = unidecode(u).lower()  # best ASCII substitutions, lowercased
20     a = re.sub(r"[^a-z0-9 -]", "", a)  # delete any other characters
21     a = a.replace(" ", "-")  # spaces to hyphens
22     a = re.sub(r"-+", "-", a)  # condense repeated hyphens
23     return a
24
25
26 def get_book_info():
27     questions = [
28         inquirer.List(
29             "entry_type",
30             message="What type of book is this?",
31             choices=[
32                 "one I’ve read",
33                 "one I’m currently reading",
34                 "one I want to read",
35             ],
36         ),
37         inquirer.Text("title", message="What’s the title of the book?"),
38         inquirer.Text("author", message="Who’s the author?"),
39         inquirer.Text("publication_year", message="When was it published?"),
40         inquirer.Text("cover_image_url", message="What’s the cover URL?"),
41         inquirer.Text("cover_desc", message="What’s the cover?"),
42         inquirer.Text("isbn10", message="Do you know the ISBN-10?"),
43         inquirer.Text("isbn13", message="Do you know the ISBN-13?"),
44     ]
45
46     answers = inquirer.prompt(questions)
47
48     answers["entry_type"] = {
49         "one I’ve read": "reviews",
50         "one I’m currently reading": "currently_reading",
51         "one I want to read": "plans",
52     }[answers["entry_type"]]
53
54     return answers
55
56
57 def get_review_info():
58     date_read_question1 = [
59         inquirer.List(
60             "date_read",
61             message="When did you finish reading it?",
62             choices=["today", "yesterday", "another day"],
63         )
64     ]
65
66     date_read = inquirer.prompt(date_read_question1)["date_read"]
67
68     today = datetime.datetime.now()
69
70     if date_read == "today":
71         date_read = today.date()
72     elif date_read == "yesterday":
73         yesterday = today - datetime.timedelta(days=1)
74         date_read = yesterday.date()
75     else:
76         date_read_question2 = [
77             inquirer.Text("date_read", message="When did you finish reading it?")
78         ]
79
80         date_read = inquirer.prompt(date_read_question2)["date_read"]
81
82         if re.match(r"^\d{4}-\d{2}-\d{2}$", date_read.strip()):
83             date_read = datetime.datetime.strptime(date_read, "%Y-%m-%d").date()
84         elif re.match(r"^\d{1,2} [A-Z][a-z]+ \d{4}$", date_read.strip()):
85             date_read = datetime.datetime.strptime(date_read, "%d %B %Y").date()
86         else:
87             sys.exit("Unrecognised date: {date_read}")
88
89     other_questions = [
90         inquirer.List(
91             "rating",
92             message="When’s your rating?",
93             choices=["★★★★★", "★★★★☆", "★★★☆☆", "★★☆☆☆", "★☆☆☆☆"],
94         ),
95         inquirer.Text("format", message="What format did you read it in?"),
96     ]
97
98     answers = inquirer.prompt(other_questions)
99     format = answers["format"]
100
101     rating = int(answers["rating"].count("★"))
102     assert 1 <= rating <= 5
103
104     if rating > 3:
105         did_not_finish = False
106     else:
107         questions = [
108             inquirer.List(
109                 "did_you_finish",
110                 message="Did you finish the book?",
111                 choices=["yes", "no"],
112             ),
113         ]
114
115         did_not_finish = inquirer.prompt(questions)["did_you_finish"] == "no"
116
117     return {
118         "date_read": date_read,
119         "rating": rating,
120         "format": format,
121         "did_not_finish": did_not_finish,
122     }
123
124
125 def save_cover(slug, cover_image_url):
126     filename, headers = urlretrieve(cover_image_url)
127
128     if headers["Content-Type"] == "image/jpeg":
129         extension = ".jpg"
130     elif headers["Content-Type"] == "image/png":
131         extension = ".png"
132     elif headers["Content-Type"] == "image/gif":
133         extension = ".gif"
134     else:
135         print(headers)
136         assert 0
137
138         url_path = hyperlink.URL.from_text(cover_image_url).path
139         extension = os.path.splitext(url_path[-1])[-1]
140
141     cover_name = f"{slug}{extension}"
142     dst_path = f"src/covers/{cover_name}"
143
144     if not os.path.exists(dst_path):
145         os.rename(filename, f"src/covers/{cover_name}")
146
147     return cover_name
148
149
150 if __name__ == "__main__":
151     book_info = get_book_info()
152
153     slug = slugify(book_info["title"])
154
155     cover_name = save_cover(slug=slug, cover_image_url=book_info["cover_image_url"])
156
157     new_entry = {
158         "book": {
159             "title": book_info["title"],
160             "author": book_info["author"],
161             "publication_year": book_info["publication_year"],
162             "cover_image": cover_name,
163         }
164     }
165
166     for key in ("cover_desc", "isbn10", "isbn13"):
167         if book_info[key]:
168             new_entry["book"][key] = book_info[key]
169
170     if book_info["entry_type"] == "reviews":
171         review_info = get_review_info()
172
173         new_entry["review"] = {
174             "date_read": review_info["date_read"],
175             "rating": review_info["rating"],
176             "format": review_info["format"],
177         }
178
179         if review_info["did_not_finish"]:
180             new_entry["review"]["did_not_finish"] = True
181
182         year = review_info["date_read"].year
183         out_dir = f"reviews/{year}"
184     elif book_info["entry_type"] == "plans":
185         new_entry["plan"] = {
186             "date_added": datetime.datetime.now().date(),
187         }
188         out_dir = "plans"
189     else:
190         out_dir = book_info["entry_type"]
191
192     out_path = os.path.join("src", out_dir, f"{slug}.md")
193     os.makedirs(os.path.dirname(out_path), exist_ok=True)
194
195     with open(out_path, "wb") as out_file:
196         frontmatter.dump(frontmatter.Post(content="", **new_entry), out_file)
197         out_file.write(b"\n")
198
199     subprocess.check_call(["open", out_path])
200
201     from render_html import main
202
203     main()