#!/usr/bin/env python
"""
This script creates a coloured calendar view that highlights days when
I did/didn't write a journal entry.
"""
import calendar
import colorsys
import datetime
import glob
import itertools
import os
import random
import tempfile
import webbrowser
def get_file_paths_under(root):
"""Generates the paths to every file under ``root``."""
if not os.path.isdir(root):
raise ValueError(f"Cannot find files under non-existent directory: {root!r}")
for dirpath, _, filenames in os.walk(root):
for f in filenames:
if os.path.isfile(os.path.join(dirpath, f)):
yield os.path.join(dirpath, f)
def get_date_of_journal_entry(path):
if not path.endswith(".md"):
return
try:
# The first line of a file should be something like 'date: 2021-05-09'
header = next(open(path))
d = datetime.datetime.strptime(header.strip(), "date: %Y-%m-%d")
except (StopIteration, ValueError):
return
else:
return d.date()
class JournalCalendar(calendar.HTMLCalendar):
def formatday(self, theyear, themonth, day, weekday):
"""
Return a day as a table cell.
"""
if day == 0:
# day outside month
return '
| ' % (
theyear,
themonth,
day,
self.cssclass_noday,
)
else:
return '%d | ' % (
theyear,
themonth,
day,
self.cssclasses[weekday],
day,
)
def formatweekday(self, day):
"""
Return a weekday name as a table header.
"""
day_abbr = {
0: "M",
1: "Tu",
2: "W",
3: "Th",
4: "F",
5: "Sa",
6: "Su",
}
return '%s | ' % (
self.cssclasses_weekday_head[day],
day_abbr[day],
)
def formatweek(self, theyear, themonth, theweek):
"""
Return a complete week as a table row.
"""
s = "".join(self.formatday(theyear, themonth, d, wd) for (d, wd) in theweek)
return "%s
" % s
def formatmonth(self, theyear, themonth, withyear=True):
"""
Return a formatted month as a table.
"""
v = []
a = v.append
a(
''
% (self.cssclass_month)
)
a("\n")
a(self.formatmonthname(theyear, themonth, withyear=withyear))
a("\n")
a(self.formatweekheader())
a("\n")
for week in self.monthdays2calendar(theyear, themonth):
a(self.formatweek(theyear, themonth, week))
a("\n")
a("
")
a("\n")
return "".join(v)
def render_calendar(dates):
lines = ["Journal progress"]
min_year = min(d.year for d in dates)
today = datetime.date.today()
yesterday = datetime.date.today() - datetime.timedelta(days=1)
if max(dates) == today:
lines.append("You've already journalled today. Well done!
")
elif max(dates) == yesterday:
lines.append("Your last journal entry was yesterday. Keep it up!
")
else:
lines.append("Your last journal entry was %s.
" % max(dates).strftime("%d %m %Y"))
streak = 0
t = today if today in dates else yesterday
while t in dates:
streak += 1
t -= datetime.timedelta(days=1)
if streak == 0:
lines.append("You don't have a streak.
")
else:
lines.append("Your current streak is %d day%s.
" % (streak, "s" if streak > 1 else ""))
for year in range(today.year, min_year - 1, -1):
lines.append(f'')
lines.append(JournalCalendar().formatyear(year, 6))
by_year = {d: v for d, v in dates.items() if d.year == year}
r = random.Random(year)
hue = r.random()
r, g, b = colorsys.hsv_to_rgb(hue, 1, 1)
try:
max_by_year = max(by_year.values())
except ValueError:
pass
lines.append("
")
lines.append("")
lines.append("")
return "\n".join(lines)
if __name__ == "__main__":
dates = {
get_date_of_journal_entry(f): os.stat(f).st_size
for f in get_file_paths_under(".")
}
del dates[None]
_, out_path = tempfile.mkstemp(suffix=".html")
with open(out_path, "w") as out_file:
out_file.write(render_calendar(dates))
webbrowser.open(f"file://{out_path}")