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.
132 lines
3.7 KiB
Python
Executable file
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()
|