Have some months that I am trying to organize my daily tasks, and the first thing that I tried was to create a time board, where I put what I wanted to do on my day.
First I started with 50 minutes of computer science where I studied things like Data Structures, Algorithms, etc.
Then after that I reserve 50 minutes to learn about Hacking, things like how can I secure systems, the basics things, like network, tools, etc.
For improve my english and speaking skills I read a book for 30 minutes, in high voice.
Another important things is to make exercises for improve my body and mind, then some push-ups, squat, etc.
I decided too to make some networking in Linkedin, but I need to confess that I don't do it any time yet, but I will.
But, you know the problem with it? the time that I reserved for each thing, although is good to have a time limit, sometimes I was very focused studding about computer science and then the alarm trigger and I lost all the focus, or sometimes I am not focused and then the alarm trigger and I see that i lost the 50 minutes thinking about another thing or doing another thing.
Then I decided to stop using timers and start finishing tasks and subtasks, basically instead of read 30 minutes every day, I will read 5 pages per day, instead of 50 minutes of computer science I will choose one or two topics and study.
But where I will organize those tasks? Currently my full setup is in the terminal, my code editor (nvim), my git (lazygit), my code agent (opencode), docker (lazydocker).
And I discovered a tool called Task Warrior, that is a simple task organizer but I have a problem, creating tasks and subtasks in the Task Warrior is king boring, then why not create an automation for it?
Basically we have two scripts, one in Bash and the other in Python.
The script in Bash will do the following:
- Read a MD file
- Get check-boxes and convert in Tasks
- Case the checkbox is a sub checkbox then it is a child or subtask.
- 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 :)
Top comments (0)