VOOZH about

URL: https://dev.to/tjuliu/how-to-organize-your-daily-task-with-task-warrior-2baa

⇱ How to organize your daily task with Task Warrior - DEV Community


Basically we have two scripts, one in Bash and the other in Python.

The script in Bash will do the following:

  1. Read a MD file
  2. Get check-boxes and convert in Tasks
  3. Case the checkbox is a sub checkbox then it is a child or subtask.
  4. In the end I need to know which task was added then I put a logic for insert the UUID from the Task into the markdown line of the task
#!/usr/bin/env bash

set -euo pipefail

if ! command -v awk >/dev/null; then
 echo "awk is required"
 exit 1
fi

if [ $# -lt 1 ]; then
 echo "Usage: $0 <markdown-file> <project-name>"
 exit 1
fi

FILE="$1"

if [ ! -f "$FILE" ]; then
 echo "File not found: $FILE"
 exit 1
fi

PROJECT="${2:-}"

echo "=== Taskwarrior Markdown Importer ==="

if [ -z "$PROJECT" ]; then
 while true; do
 read -rp "Project name: " INPUT_PROJECT

 if [ ! -z "$INPUT_PROJECT" ]; then
 PROJECT="$INPUT_PROJECT"
 break
 fi
 done
fi

echo "Project: $PROJECT, File: $FILE"

get_task() {
 awk -v line="$1" '
 BEGIN {
 gsub(/\r/, "", line)
 gsub(/\t/, " ", line)

 match(line, /^ */)
 indent = int(RLENGTH / 4)

 trimmed = substr(line, RLENGTH + 1)

 checked = "false"

 if (match(trimmed, /^[-*+][[:space:]]*\[([xX ])\][[:space:]]*/)) {
 if (substr(trimmed, RSTART+2, 3) ~ /[xX]/) {
 checked = "true"
 }

 trimmed = substr(trimmed, RLENGTH + 1)

 while (match(trimmed, /\[\[[^]]+\]\]/)) {
 link = substr(trimmed, RSTART + 2, RLENGTH - 4) # content inside [[ ]]
 n = split(link, tmp, /\|/)
 replacement = tmp[n] # last part (after | if exists)

 trimmed = substr(trimmed, 1, RSTART - 1) replacement substr(trimmed, RSTART + RLENGTH)
 }

 gsub(/ +/, " ", trimmed)

 n = split(trimmed, parts, /;;/)

 description = parts[1]
 priority = (n >= 2 && parts[2] != "" ? parts[2] : "L")
 due = (n >= 3 && parts[3] != "" ? parts[3] : "today")

 printf "%d\x1f%s\x1f%s\x1f%s\x1f%s\n",
 indent, checked, description, priority, due
 }
 }'
}

insert_uuid_on_file() {
 local file="$1"
 local lineno="$2"
 local uuid="$3"

 local short="${uuid:0:8}"

 if sed -n "${lineno}p" "$file" | grep -q "task:"; then
 return
 fi

 sed -i "${lineno}s|\$| <!-- task:${short} -->|" "$file"
}

IGNORED=()

PCHECKED="false"
PID=""
CID=""
UUID=""

tmp=$(mktemp)
cp "$FILE" "$tmp"

i=0

while IFS= read -r line; do
 ((i += 1))

 task=$(get_task "$line")

 IFS=$'\x1f' read -r indent checked description priority due <<<"$task"

 if [[ -z "${description// /}" ]]; then
 continue
 fi

 child="false"

 if [[ "$indent" -gt 0 ]]; then
 child="true"
 else
 PCHECKED="$checked"
 fi

 if [[ $checked = "true" || $child = "true" && $PCHECKED = "true" || "$line" =~ task: ]]; then
 IGNORED+=("$description|$priority|$due")

 continue
 fi

 ID=$(task add "$description" project:"$PROJECT" priority:"$priority" due:"$due" mdfile:"$FILE" |
 grep -oP 'Created task \K[0-9]+')

 if [[ "$child" = "false" ]]; then
 PID="$ID"
 else
 CID="$ID"

 task "$CID" modify +P"$PID" >/dev/null

 task "$PID" modify depends:"$CID" >/dev/null
 fi

 UUID=$(task _get "$ID".uuid)

 insert_uuid_on_file "$FILE" "$i" "$UUID"

 # echo "$indent|$checked|$description|$priority|$due"
done <"$tmp"

rm "$tmp"

echo "=== IGNORED ==="
printf "%s\n" "${IGNORED[@]}"
echo "=== === === ==="

You need awk installed for use it, and I this only work in Linux and MacOS.

And you can run with ./taskmd.sh <path/to/file.md> <project_name (optional)>

In my case I created a function in my fish shell for execute this script, then I don't need to put the path to the script, maybe it can be a alias in the terminal too.

And the Python is not really necessary in this logic, cause it only do a simple logic of when I finish some task in the Task Warrior this script will be executed as a hook, and It will check the checkbox in the Markdown file.

#!/usr/bin/env python3

import json
import re
import sys
from pathlib import Path

def update_line(line: str, uuid: str, status: str):
 if f"task:{uuid}" not in line:
 return line, False

 # line = re.sub(r"\s*<!--\s*task:" + re.escape(uuid) + r"\s*-->", "", line)

 if status in ("completed", "deleted"):
 line = re.sub(r"\[\s\]", "[x]", line)
 else:
 line = re.sub(r"\[[xX]\]", "[ ]", line)
 return line, True

def main():
 _ = json.loads(sys.stdin.readline())
 new_task = json.loads(sys.stdin.readline())

 status = new_task.get("status", "")

 if status not in ("completed", "deleted"):
 print(json.dumps(new_task))
 return

 uuid = new_task.get("uuid", "")
 if not uuid:
 print(json.dumps(new_task))
 return

 uuid_short = uuid[:8]

 mdfile = new_task.get("mdfile")
 if not mdfile:
 print(json.dumps(new_task))
 return

 path = Path(mdfile)
 if not path.exists():
 print(json.dumps(new_task))
 return

 lines = path.read_text().splitlines()
 changed = False

 for i, line in enumerate(lines):
 new_line, matched = update_line(line, uuid_short, new_task.get("status"))
 if matched:
 lines[i] = new_line
 changed = True
 break

 if changed:
 path.write_text("\n".join(lines) + "\n")

 print(json.dumps(new_task))

if __name__ == "__main__":
 main()

This python script need to stay in ~/.task/hooks with name like on-modify.<anyname>.pyon Linux case, you need to verify in Windows where this paste need to be.

And you need to add those lines in the .taskrc file, that on Linux, will stay in the ~ directory.

uda.mdfile.type=string
uda.mdfile.label=File

In that way I can convert markdown files, that is very simple to create in tasks on my Task Warrior.

The task warrior you can download here and I recommend to use the Task Warrior TUI for have a better visualization in the terminal.

Is that, have a nice day, and see you soon :)