import logging import os import sys from datetime import date, datetime, timedelta from random import randint from caldav import DAVClient from dotenv import find_dotenv, load_dotenv from printer import p logging.basicConfig( format="{asctime} {levelname:8} {name}: {message}", datefmt="%Y-%m-%d %H:%M:%S", style="{", stream=sys.stdout, force=True, ) log = logging.getLogger("todo-receipt") log.setLevel(logging.DEBUG) if load_dotenv(find_dotenv(usecwd=True)): log.debug("Loaded .env") else: log.debug("Didn't find .env") CALDAV_URL = os.getenv("CALDAV_URL") CALDAV_USERNAME = os.getenv("CALDAV_USERNAME") CALDAV_PASSWORD = os.getenv("CALDAV_PASSWORD") day = timedelta(days=1) week = timedelta(days=7) today = date.today() yesterday = today - day tomorrow = today + day next_week = today + week def coerce_date(datetime): try: return datetime.date() except: return datetime def shuffle_with_priority(tasks): return sorted( tasks, key=lambda task: ( task.icalendar_component.get("priority", 5), randint(0, 10), ), ) with DAVClient( url=CALDAV_URL, username=CALDAV_USERNAME, password=CALDAV_PASSWORD ) as client: log.debug("Loading calendars") principal = client.principal() calendars = principal.calendars() events = [] todos = [] for c in calendars: log.debug(f"Fetching from {c.name}") if c.name not in ["Schedule"]: log.debug("Fetching events and todos") events.extend(c.events()) todos.extend(c.todos()) else: log.debug("Finding cancelled classes") for event in c.events(): if event.icalendar_component.get("status") == "CANCELED": log.debug( f"Found cancelled class {event.icalendar_component.get('summary')}" ) events.append(event) must_do = [] should_do = [] want_to_do = [] log.debug("Processing todos") for task in todos: due = task.get_due() if due is None: want_to_do.append(task) continue task.expand_rrule(yesterday, next_week) due = coerce_date(due) try: start = coerce_date(task.icalendar_component.get("dtstart").dt or yesterday) except: start = yesterday if today < start: continue if due < tomorrow: must_do.append(task) elif due < next_week: should_do.append(task) else: want_to_do.append(task) scheduled = [] log.debug("Processing events") for event in events: event.expand_rrule(yesterday, next_week) start_day = coerce_date(event.icalendar_component.get("dtstart").dt) end_day = coerce_date(event.icalendar_component.get("dtend").dt) if end_day < today or today < start_day: continue else: scheduled.append(event) should_want = shuffle_with_priority(should_do) + shuffle_with_priority(want_to_do) categories = { "Must do": must_do, "Should do": should_want[:3], "Want to do": should_want[3:6], } if len(should_want) > 6: categories["Leftover"] = should_want[7:] with p: log.debug("Printing output") p.title(" TODO List ", size=4) p.subtitle(datetime.today().strftime("%Y-%m-%d")) p.ln() if scheduled: p.textln("Scheduled:") for event in scheduled: summary = event.icalendar_component["summary"] if event.icalendar_component.get("status") == "CANCELED": summary += " (CANCELED)" start = event.icalendar_component.get("dtstart").dt p.textln(f"[ ] {summary} @ {start.strftime('%H:%M')}") else: p.textln("Scheduled: (None)") for k, v in categories.items(): p.ln() if v: p.textln(f"{k}:") for task in v: p.textln(f"[ ] {task.icalendar_component['summary']}") else: p.textln(f"{k}: (None)")