#!/usr/bin/env python3 # From: https://gist.github.com/varunagrawal/2b93c5dc721520ff6876e940c420ab05 # This hooks script logs any finished tasks currently in the todo.txt file # pointed to by its settings. # The on-exit event is triggered once, after all processing is complete. import re import os import sys import subprocess import options as opts import json from datetime import datetime TODAY = datetime.today().strftime("%Y-%m-%d") def get_todo_block_date(line, todo_block_title): result = re.search( rf"^\[(\d{{4}}-\d{{2}}-\d{{2}}).*\] ({re.escape(todo_block_title)}$)?", line, ) # we are entering todotxt data block if result and result[2]: return result[1] # we enter block but not one for todotxt elif result and not result[2]: return False # nothing changed, we didn't cross a date line return "same" def process_file(options): fname = options.jrnl_fname lines_to_delete = [] today_exists = False curdate = "" with open(fname) as file: line_number = 0 for line in file: line_number += 1 if handle_idea(line, options): lines_to_delete.append(line_number) todo_block = get_todo_block_date(line, options.todo_block_title) if todo_block == False: curdate = "" continue if todo_block != "same": curdate = todo_block continue if not curdate: continue if handle_completed_tasks(line, curdate, options): lines_to_delete.append(line_number) if handle_open_tasks(line, curdate, options): lines_to_delete.append(line_number) if is_today(curdate): today_exists = True if lines_to_delete: delete_lines_from_file(fname, lines_to_delete, options.dryrun) if not today_exists and options.taskwarrior_fill_today: add_today(fname, options) def handle_completed_tasks(line, date, options): completed_task = re.search( rf"^{options.regex_task_completed} ((?:\([A-D]\))? ?.*)$", line, ) if completed_task and options.taskwarrior_log_completed: log_completed_to_taskwarrior(completed_task[1], date, options) return True return False def log_completed_to_taskwarrior(task_string, date, options): overrides = options.taskwarrior_overrides cmd = [ "task", *overrides, "log", task_string, f"entry:{date}", f"end:{date}", get_prio_string(task_string), ] if options.dryrun: print(cmd) return subprocess.run(cmd) def handle_open_tasks(line, date, options): completed_task = re.search( rf"^{options.regex_task_incomplete} ((?:\([A-D]\))? ?.*)$", line, ) if completed_task and options.taskwarrior_add_incomplete: add_incomplete_to_taskwarrior(completed_task[1], date, options) return True return False def add_incomplete_to_taskwarrior(task_string, date, options): overrides = options.taskwarrior_overrides cmd = [ "task", *overrides, "add", task_string, f"entry:{date}", f"scheduled:{date}", get_prio_string(task_string), ] if options.dryrun: print(cmd) return subprocess.run(cmd) def handle_idea(line, options): completed_task = re.search( rf"^{options.regex_task_idea} (.*)", line, ) if completed_task and options.taskwarrior_add_ideas: add_idea_to_taskwarrior(completed_task[1], options) return True return False def add_idea_to_taskwarrior(idea_string, options): overrides = options.taskwarrior_overrides cmd = [ "task", *overrides, "add", "+idea", "+maybe", idea_string, ] if options.dryrun: print(cmd) return subprocess.run(cmd) def get_prio_string(task_string): prio = re.search(r"^\(([ABC])\) (.*)$", task_string) prio_string = "" if prio: task_string = prio[2] if prio[1] == "A": prio_string = "prio:H" elif prio[1] == "B": prio_string = "prio:M" elif prio[1] == "C": prio_string = "prio:L" return prio_string def delete_lines_from_file(fname, lines, dryrun): if dryrun: print(f"deleting lines: {lines}") return cur_line = 0 repl_file = fname + ".bak" with open(fname) as read_file, open(repl_file, "w") as write_file: for line in read_file: cur_line += 1 if lines and cur_line == lines[0]: lines.pop(0) continue write_file.write(line) os.rename(repl_file, fname) def is_today(cur_date): if cur_date == TODAY: return True return False def add_today(fname, options): cmd = ["task", *options.taskwarrior_overrides, "+TODAY or +OVERDUE", "export"] due_json = json.loads(subprocess.run(cmd, capture_output=True).stdout) tasks = f"[{TODAY} 09:00] {options.todo_block_title}\n" for task in due_json: tasks += f"{task['description']}\n" tasks += "\n" if options.dryrun: print(f"\nWRITING TODAY:\n{tasks}") return repl_fname = fname + ".bak" with open(fname, "r") as read_file, open(repl_fname, "w") as write_file: write_file.write(tasks) write_file.write(read_file.read()) os.rename(repl_fname, fname) if __name__ == "__main__": process_file(opts.init()) sys.exit(0)