topen/topen.py
Marty Oehme 1ec3755344
chore: Simplify funcs by removing default args
In preparation for reading the config also from a taskrc file I want to
simplify the configuration management beforehand. There should be one
place where we grab it from the environment, one place to grab from CLI
and then one place from conf file. This helps with simplifying a little
by not again injecting defaults at the mid-point.

Ultimately, we should create one 'config' data structure, probably dict
or NameSpace which we pass around and each function receives it.
2025-04-07 10:49:07 +02:00

132 lines
3.7 KiB
Python
Executable file

#!/usr/bin/env python
# Open or create a note file
# for a taskwarrior task.
# Takes a taskwarrior ID or UUID for a single task.
# Edits an existing task note file,
# or creates a new one.
# It currently assumes an XDG-compliant taskwarrior configuration by default.
import argparse
import os
import subprocess
import sys
from pathlib import Path
from tasklib import Task, TaskWarrior
# TODO: This should not assume XDG compliance for
# no-setup TW instances.
TASK_RC = os.getenv("TASKRC", "~/.config/task/taskrc")
TASK_DATA_DIR = os.getenv("TASKDATA", "~/.local/share/task")
TOPEN_DIR = os.getenv("TOPEN_DIR", "~/.local/share/task/notes")
TOPEN_EXT = os.getenv("TOPEN_EXT", "md")
TOPEN_ANNOT = os.getenv("TOPEN_ANNOT", "Note")
TOPEN_EDITOR = os.getenv("EDITOR") or os.getenv("VISUAL", "nano")
TOPEN_QUIET = os.getenv("TOPEN_QUIET", False)
def parse_cli() -> argparse.Namespace:
parser = argparse.ArgumentParser(
formatter_class=argparse.RawDescriptionHelpFormatter,
description="Taskwarrior note editing made easy.",
epilog="""Provide a taskwarrior task id or uuid and topen creates a
new note file for or lets you edit an existing one.
Additionally it adds a small annotation to the task
to let you see that there exists a note file next time
you view the task.
""",
)
_ = parser.add_argument(
"id", help="The id/uuid of the taskwarrior task for which we edit notes"
)
_ = parser.add_argument(
"-d",
"--notes-dir",
default=TOPEN_DIR,
help="Location of topen notes files",
)
_ = parser.add_argument(
"--quiet",
default=TOPEN_QUIET,
action="store_true",
help="Silence any verbose displayed information",
)
_ = parser.add_argument(
"--extension", default=TOPEN_EXT, help="Extension of note files"
)
_ = parser.add_argument(
"--annotation",
default=TOPEN_ANNOT,
help="Annotation content to set within taskwarrior",
)
_ = parser.add_argument(
"--task-data", default=TASK_DATA_DIR, help="Location of taskwarrior data"
)
_ = parser.add_argument(
"--editor", default=TOPEN_EDITOR, help="Program to open note files with"
)
return parser.parse_args()
IS_QUIET = False
def whisper(text: str) -> None:
if not IS_QUIET:
print(text)
def main():
args = parse_cli()
if not args.id:
_ = sys.stderr.write("Please provide task ID as argument.\n")
if args.quiet:
global IS_QUIET
IS_QUIET = True
task = get_task(id=args.id, data_location=args.task_data)
uuid = task["uuid"]
if not uuid:
_ = sys.stderr.write(f"Could not find task for ID: {args.id}.")
sys.exit(1)
fname = get_notes_file(uuid, notes_dir=args.notes_dir, notes_ext=args.extension)
open_editor(fname, editor=args.editor)
add_annotation_if_missing(task, annotation_content=args.annotation)
def get_task(id: str, data_location: str) -> Task:
tw = TaskWarrior(data_location)
try:
t = tw.tasks.get(id=id)
except Task.DoesNotExist:
t = tw.tasks.get(uuid=id)
return t
def get_notes_file(uuid: str, notes_dir: str, notes_ext: str) -> Path:
return Path(notes_dir).joinpath(f"{uuid}.{notes_ext}")
def open_editor(file: Path, editor: str) -> None:
_ = whisper(f"Editing note: {file}")
proc = subprocess.Popen(f"{editor} {file}", shell=True)
_ = proc.wait()
def add_annotation_if_missing(task: Task, annotation_content: str) -> None:
for annot in task["annotations"] or []:
if annot["description"] == annotation_content:
return
task.add_annotation(annotation_content)
_ = whisper(f"Added annotation: {annotation_content}")
if __name__ == "__main__":
main()