From 9589d0a9f0127b76dbbc74f4d4f6c72339ace600 Mon Sep 17 00:00:00 2001 From: Infinidoge Date: Fri, 31 Jan 2025 02:04:37 -0500 Subject: [PATCH 1/4] format with nixfmt --- shell.nix | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/shell.nix b/shell.nix index 6588b4a..f090023 100644 --- a/shell.nix +++ b/shell.nix @@ -23,9 +23,11 @@ in # alias print="lp -d EPSON-TM-m30 -o lpi=10 -o cpi=15" devshell.mkShell { packages = with pkgs; [ - (python3.withPackages (p: with p; [ - python-escpos - pycups - ])) + (python3.withPackages ( + p: with p; [ + python-escpos + pycups + ] + )) ]; } From c99f6b507e3cbc21bc856af9b39dfea8d2bdab4b Mon Sep 17 00:00:00 2001 From: Infinidoge Date: Fri, 31 Jan 2025 02:21:29 -0500 Subject: [PATCH 2/4] add caldav to shell --- shell.nix | 1 + 1 file changed, 1 insertion(+) diff --git a/shell.nix b/shell.nix index f090023..52a99ee 100644 --- a/shell.nix +++ b/shell.nix @@ -27,6 +27,7 @@ devshell.mkShell { p: with p; [ python-escpos pycups + caldav ] )) ]; From ec86c1c30742b719474268c9769b428fb2e019b0 Mon Sep 17 00:00:00 2001 From: Infinidoge Date: Fri, 31 Jan 2025 02:22:29 -0500 Subject: [PATCH 3/4] add python-dotenv --- shell.nix | 1 + 1 file changed, 1 insertion(+) diff --git a/shell.nix b/shell.nix index 52a99ee..fae4627 100644 --- a/shell.nix +++ b/shell.nix @@ -28,6 +28,7 @@ devshell.mkShell { python-escpos pycups caldav + python-dotenv ] )) ]; From 42062c2eb6cd825618ef91d340cd615377013ee3 Mon Sep 17 00:00:00 2001 From: Infinidoge Date: Fri, 31 Jan 2025 02:27:41 -0500 Subject: [PATCH 4/4] add daily todo printer --- daily-todo.py | 168 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 168 insertions(+) create mode 100644 daily-todo.py diff --git a/daily-todo.py b/daily-todo.py new file mode 100644 index 0000000..efb7239 --- /dev/null +++ b/daily-todo.py @@ -0,0 +1,168 @@ +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) + + start = coerce_date(task.icalendar_component.get("dtstart").dt or 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_do = shuffle_with_priority(should_do) + want_to_do = shuffle_with_priority(want_to_do) + + while len(must_do) < 2: + if should_do: + must_do.append(should_do.pop(0)) + elif want_to_do: + must_do.append(want_to_do.pop(0)) + else: + break # No remaining tasks + + while len(should_do) < 3: + if want_to_do: + should_do.append(want_to_do.pop(0)) + else: + break # No remaining tasks + + categories = { + "Must do": must_do, + "Should do": should_do[:3], + "Want to do": want_to_do[:3], + } + +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)")