summary refs log tree commit diff
path: root/bin/todo
diff options
context:
space:
mode:
authorRobert Günzler <r@gnzler.io>2026-04-08 13:18:14 +0900
committerRobert Günzler <r@gnzler.io>2026-04-08 13:18:14 +0900
commitb61b58ce2c1371d6f904f108fa7feed3abfbe5c7 (patch)
tree008a5cf54ecbfffb97ddac30e44c8ab224395ab0 /bin/todo
parentd4620f3ac119263fee90bd819eadaf84fc8d50b7 (diff)
*
Automated-commit-by: dots
Signed-off-by: Robert Günzler <r@gnzler.io>
Diffstat (limited to 'bin/todo')
-rwxr-xr-xbin/todo216
1 files changed, 216 insertions, 0 deletions
diff --git a/bin/todo b/bin/todo
new file mode 100755
index 0000000..7436779
--- /dev/null
+++ b/bin/todo
@@ -0,0 +1,216 @@
+#!/bin/bash
+set -e
+[ -n "$DEBUG" ] && set -x
+
+_todo_files=("todo.txt" "TODO")
+_todo_file=""
+
+function has_todo_file() {
+	cwd=$(pwd)
+	for f in "${_todo_files[@]}"; do
+		test -f "$cwd/$f" && _todo_file="$cwd/$f" && return 0
+	done
+	return 1
+}
+
+function print_org() {
+	awk '
+function filename() {
+    n = split(FILENAME, a, "/")
+    return a[n]
+}
+function res() {
+    return "\033[0m"
+}
+function bold(s) {
+    return "\033[1m" s "\033[22m"
+}
+function strike(s) {
+    return "\033[9m" s "\033[29m"
+}
+function red(s) {
+    return "\033[31m" s res()
+}
+function green(s) {
+    return "\033[32m" s res()
+}
+function blue(s) {
+    return "\033[34m" s res()
+}
+function magenta(s) {
+    return "\033[35m" s res()
+}
+function osc8(uri, s) {
+    return "\033]8;;" uri "\033\\\\" s "\033]8;;\033\\"
+}
+function link(uri) {
+    return osc8(uri, "\033[2m" uri res())
+}
+{
+    if ($1 ~ /^#/) next
+
+    // support todotxt files
+    if (filename() == "todo.txt") {
+        status = "TODO"
+        if ($1 ~ /^x/) {
+            status = "DONE"
+            $0 = gensub(/^[x] /, "", "1")
+            $0 = strike($0)
+        }
+
+	$0 = gensub(/ (\+[^ ]+)/, bold(blue(" \\1")), "g")
+        $0 = gensub(/ (@[^ ]+)/, bold(blue(" \\1")), "g")
+        $0 = gensub(/(https?:\/\/[^ ]+)/, link("\\1"), "g")
+        tasks[status][i++] = $0
+        next
+    }
+
+    // support TODO files
+    if ($1 ~ /^(-|*)/) {
+        // detect status strings
+        match($0, /\s(TODO|WIP|WAITING|DONE)\s/, arr)
+
+        if (filename() == "TODO" && arr[1] == "") {
+	    $0 = gensub(/(^[\w]*(-|\*))/, "\\1 TODO", "1")
+        }
+
+        $0 = gensub(/(TODO)/, red("\\1"), "1")
+        $0 = gensub(/(WIP)/, blue("\\1"), "1")
+        $0 = gensub(/(DONE)/, green("\\1"), "1")
+        $0 = gensub(/(\?\?\?)/, bold(magenta("\\1")), "1")
+        tasks[arr[1]][i++] = $0
+    }
+}
+END {
+    order[1] = "TODO"
+    order[2] = "WIP"
+    order[3] = "DONE"
+    for (i in order) {
+        for (t in tasks[order[i]]) {
+            print "  " tasks[order[i]][t]
+        }
+    }
+}' $1
+}
+
+function print_hr() {
+	local str cols
+	local start line end
+
+	str="$1"
+	[[ -n $str ]] && str=" $str "
+	cols=$((${COLUMNS:-$(tput cols)} - ${#str} - 3))
+	start=$'\e(0'
+	end=$'\e(B'
+	line="qqq"
+	while ((${#line} < $cols)); do line+=$line; done
+
+	printf "╾"
+	printf "%s%s%s" "$start" "${line:0:$((cols / 2))}" "$end"
+	printf "%s%s%s" "$start" "${line:0:$((cols / 2))}" "$end"
+	printf "%s" "$str"
+	printf "%s%s%s" "$start" "${line:0:1}" "$end"
+	printf "╼"
+	printf "\n"
+}
+
+function print_shell_hook() {
+	case $1 in
+	bash)
+		cat <<-EOF
+			# Hook to run todo on directory change.
+			__todo_oldpwd="\$(\builtin pwd -L)"
+
+			function __todo_hook() {
+			    \builtin local -r retval="$?"
+			    \builtin local pwd_tmp
+			    pwd_tmp="\$(\builtin pwd -L)"
+			    if [[ \${__todo_oldpwd} != "\${pwd_tmp}" ]]; then
+			        __todo_oldpwd="\${pwd_tmp}"
+			        \command todo
+			    fi
+			    return "\${retval}"
+			}
+
+			# Initialize hook.
+			if [[ \${PROMPT_COMMAND:=} != *'__todo_hook'* ]]; then
+			    PROMPT_COMMAND="__todo_hook;\${PROMPT_COMMAND#;}"
+			fi
+
+			# Run at startup.
+			\command sleep .1 && \command todo
+		EOF
+		;;
+	zsh)
+		cat <<-EOF
+			__todo_oldpwd="$(\builtin pwd -L)"
+			function __todo_hook() {
+				\builtin local -r retval="$?"
+				\builtin local pwd_tmp
+				if [[ \${__todo_oldpwd} != "\${pwd_tmp}" ]]; then
+					__todo_oldpwd="\${pwd_tmp}"
+					\command ${0}
+				fi
+				return "\${retval}"
+			}
+			typeset -ag precmd_functions;
+			if [[ -z \${precmd_functions[(r)__todo_hook]} ]]; then
+				precmd_functions+=__todo_hook;
+			fi
+		EOF
+		;;
+	cd)
+		echo "cd() { builtin cd \"\$@\" && $0; }"
+		;;
+	-h | --help | *)
+		echo "usage: todo hook <integration>"
+		echo ""
+		echo "use like this: eval \$(todo hook <integration>)"
+		echo ""
+		echo "supported integrations:"
+		echo " - zsh: run todo as a precmd hook"
+		echo "   (this can get quite annoying when you're in a direcory that has a todo file)"
+		echo " - cd: run todo as a post-cd hook"
+		echo ""
+		exit 1
+		;;
+	esac
+}
+
+function main() {
+	case "$1" in
+	hook)
+		shift
+		print_shell_hook "$1"
+		;;
+	-h | --help)
+		echo "usage: todo [-r] [hook <integration>]"
+		echo "A quite stupid todo/agenda script that reminds you of things you need to do"
+		echo ""
+		echo " -r, --recursive    walk the file tree, searching for todos"
+		echo " <integration>      shell integration, you should eval this"
+		echo ""
+		exit 0
+		;;
+	-r | --recursive | -R)
+		while read FILE; do
+			print_hr "[$FILE]"
+			print_org $FILE
+		done < <(rg --files ${_todo_files[@]/#/-g })
+		;;
+	*)
+		if [ -f "$1" ]; then
+			print_hr "[$1]"
+			print_org $1
+			exit 0
+		fi
+		if has_todo_file; then
+			print_hr "[$(basename $_todo_file)]"
+			print_org $_todo_file
+			print_hr
+		fi
+		;;
+	esac
+}
+
+main $@