#!/usr/bin/env hy (import json) (import time [sleep]) (import threading [Thread]) (import datetime [datetime]) (import subprocess [run PIPE DEVNULL]) (setv notified-tasks #{}) (defn notify-send [title desc [time 0] [actions []]] (let [cmd ["notify-send" title desc "-t" (str time)]] (for [action actions] (list.append cmd "-A") (list.append cmd action)) (let [result (run cmd :stdout PIPE :text True)] (try (int result.stdout) (except [ValueError] None))))) (defn get-tasks [] (let [todo-proc (run ["todo" "--porcelain" "list"] :stdout PIPE :text True)] (json.loads todo-proc.stdout))) (defn mark-task-done [task] (run ["todo" "done" (str (get task "id"))] :stdout DEVNULL)) (defn hash-task [task] (hash (+ (str (get task "start")) (str (get task "due")) (str (get task "description")) (str (get task "summary"))))) (defn notify-task [task timestamp message] (let [summary (get task "summary") time-obj (datetime.fromtimestamp timestamp) time-str (datetime.strftime time-obj "%Y/%m/%d %H:%M") result (notify-send (if summary summary "No Summary") (+ "Task " message " at " time-str) :actions ["Mark Done"])] (when (= result 0) (mark-task-done task)))) (defn get-notify-time-and-message [task] (if (is-not (get task "start") None) #("Starts" (get task "start")) #("Due" (get task "due")))) (defn notify-tasks [tasks] (global notified-tasks) (let [now (int (datetime.timestamp (datetime.now))) notified #{}] (for [task tasks] (let [[message time] (get-notify-time-and-message task)] (when (and (not (get task "completed")) (is-not time None) (<= (- time now) 300)) (let [task-hash (hash-task task)] (when (not-in task-hash notified-tasks) (Thread.start (Thread :target notify-task :args #(task time message)))) (set.add notified task-hash))))) (setv notified-tasks notified))) (while True (notify-tasks (get-tasks)) (sleep 60))