#!/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 " echo "" echo "use like this: eval \$(todo hook )" 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 ]" 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 " 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 $@