"Plan to read" books are now ordered by date added
[books.alexwlchan.net] / scripts / render_html.py
index bb0eb11..0cbb13f 100755 (executable)
@@ -1,6 +1,7 @@
 #!/usr/bin/env python
 
 import datetime
+import itertools
 import os
 import pathlib
 import re
@@ -9,9 +10,10 @@ import sys
 
 import attr
 import frontmatter
+from jinja2 import Environment, FileSystemLoader, select_autoescape
 import markdown
 from markdown.extensions.smarty import SmartyExtension
-from jinja2 import Environment, FileSystemLoader, select_autoescape
+import smartypants
 
 
 def rsync(dir1, dir2):
@@ -33,9 +35,10 @@ class Book:
 @attr.s
 class Review:
     date_read = attr.ib()
-    rating = attr.ib()
     text = attr.ib()
     format = attr.ib()
+    rating = attr.ib(default=None)
+    did_not_finish = attr.ib(default=False)
 
 
 @attr.s
@@ -45,7 +48,8 @@ class ReviewEntry:
     review = attr.ib()
 
     def out_path(self):
-        return self.path.relative_to("src").with_suffix("")
+        name = self.path.with_suffix("").name
+        return pathlib.Path(f"reviews/{name}")
 
 
 def get_review_entry_from_path(path):
@@ -68,9 +72,6 @@ class CurrentlyReadingEntry:
     book = attr.ib()
     reading = attr.ib()
 
-    def out_path(self):
-        return self.path.relative_to("src").with_suffix("")
-
 
 def get_reading_entry_from_path(path):
     post = frontmatter.load(path)
@@ -81,7 +82,36 @@ def get_reading_entry_from_path(path):
     return CurrentlyReadingEntry(path=path, book=book, reading=reading)
 
 
-def get_reviews(dirpath, constructor):
+def _parse_date(value):
+    if isinstance(value, datetime.date):
+        return value
+    else:
+        return datetime.datetime.strptime(value, "%Y-%m-%d").date()
+
+
+@attr.s
+class Plan:
+    text = attr.ib()
+    date_added = attr.ib(converter=_parse_date)
+
+
+@attr.s
+class PlanEntry:
+    path = attr.ib()
+    book = attr.ib()
+    plan = attr.ib()
+
+
+def get_plan_entry_from_path(path):
+    post = frontmatter.load(path)
+
+    book = Book(**post["book"])
+    plan = Plan(**post["plan"], text=post.content)
+
+    return PlanEntry(path=path, book=book, plan=plan)
+
+
+def get_entries(dirpath, constructor):
     for dirpath, _, filenames in os.walk(dirpath):
         for f in filenames:
             if not f.endswith(".md"):
@@ -123,7 +153,9 @@ def render_date(date_value):
 
 def render_individual_review(env, *, review_entry):
     template = env.get_template("review.html")
-    html = template.render(review_entry=review_entry)
+    html = template.render(
+        review_entry=review_entry, title=f"My review of {review_entry.book.title}"
+    )
 
     out_name = review_entry.out_path() / "index.html"
     out_path = pathlib.Path("_html") / out_name
@@ -131,7 +163,7 @@ def render_individual_review(env, *, review_entry):
     out_path.write_text(html)
 
 
-if __name__ == "__main__":
+def main():
     env = Environment(
         loader=FileSystemLoader("templates"),
         autoescape=select_autoescape(["html", "xml"]),
@@ -139,6 +171,7 @@ if __name__ == "__main__":
 
     env.filters["render_markdown"] = render_markdown
     env.filters["render_date"] = render_date
+    env.filters["smartypants"] = smartypants.smartypants
 
     rsync("src/covers/", "_html/covers/")
     rsync("static/", "_html/static/")
@@ -146,7 +179,7 @@ if __name__ == "__main__":
     # Render the "all reviews page"
 
     all_reviews = list(
-        get_reviews(dirpath="src/reviews", constructor=get_review_entry_from_path)
+        get_entries(dirpath="src/reviews", constructor=get_review_entry_from_path)
     )
     all_reviews = sorted(
         all_reviews, key=lambda rev: str(rev.review.date_read), reverse=True
@@ -156,7 +189,16 @@ if __name__ == "__main__":
         render_individual_review(env, review_entry=review_entry)
 
     template = env.get_template("list_reviews.html")
-    html = template.render(all_reviews=all_reviews)
+    html = template.render(
+        all_reviews=[
+            (year, list(reviews))
+            for (year, reviews) in itertools.groupby(
+                all_reviews, key=lambda rev: str(rev.review.date_read)[:4]
+            )
+        ],
+        title="books i’ve read",
+        this_year=str(datetime.datetime.now().year),
+    )
 
     out_path = pathlib.Path("_html") / "reviews/index.html"
     out_path.write_text(html)
@@ -164,16 +206,33 @@ if __name__ == "__main__":
     # Render the "currently reading" page
 
     all_reading = list(
-        get_reviews(dirpath="src/currently_reading", constructor=get_reading_entry_from_path)
+        get_entries(
+            dirpath="src/currently_reading", constructor=get_reading_entry_from_path
+        )
     )
 
     template = env.get_template("list_reading.html")
-    html = template.render(all_reading=all_reading)
+    html = template.render(all_reading=all_reading, title="books i’m currently reading")
 
     out_path = pathlib.Path("_html") / "reading/index.html"
     out_path.parent.mkdir(exist_ok=True, parents=True)
     out_path.write_text(html)
 
+    # Render the "want to read" page
+
+    all_plans = list(
+        get_entries(dirpath="src/plans", constructor=get_plan_entry_from_path)
+    )
+
+    all_plans = sorted(all_plans, key=lambda plan: plan.plan.date_added)
+
+    template = env.get_template("list_plans.html")
+    html = template.render(all_plans=all_plans, title="books i want to read")
+
+    out_path = pathlib.Path("_html") / "to-read/index.html"
+    out_path.parent.mkdir(exist_ok=True, parents=True)
+    out_path.write_text(html)
+
     # Render the front page
 
     index_template = env.get_template("index.html")
@@ -181,3 +240,9 @@ if __name__ == "__main__":
 
     index_path = pathlib.Path("_html") / "index.html"
     index_path.write_text(html)
+
+    print("✨ Rendered HTML files to _html ✨")
+
+
+if __name__ == "__main__":
+    main()