diff options
| -rw-r--r-- | README.org | 70 | ||||
| -rwxr-xr-x | build.sh | 101 | ||||
| -rw-r--r-- | tasker.1 | 98 | ||||
| -rw-r--r-- | tasker.bash | 90 |
4 files changed, 348 insertions, 11 deletions
@@ -1,16 +1,72 @@ #+AUTHOR: David Moc #+EMAIL: personal@cdatgoose.org - * Tasker -Tasker is a simple task tracker (with built in fish completion) written in Haskell. + +Tasker is a simple task tracker written in Haskell. + +It includes optional shell completion support for: + +- fish +- bash * Install -You can eiter use the `./build.sh` that installs into: -- `/usr/local/bin/tasker` -- `/usr/share/fish/vendor_completions.d/tasker.fish` -Or just run: + +** Installer: + +#+begin_src shell + ./install.sh +#+end_src + +This builds the executable with Cabal and installs it to: + +- =/usr/local/bin/tasker= + +The installer also detects your current shell and installs the matching completion file when available. + +For fish, it installs: + +- =/usr/share/fish/vendor_completions.d/tasker.fish= + +For bash, it installs: + +- =/usr/share/bash-completion/completions/tasker= + +If a manpage is available, it is installed to: + +- =/usr/local/share/man/man1/tasker.1= + +** Manual: + +#+begin_src shell + ./install.sh --shell fish + ./install.sh --shell bash + ./install.sh --shell all + ./install.sh --shell none +#+end_src + +To skip installing the manpage: + +#+begin_src shell + ./install.sh --no-man +#+end_src + +* Build only + +To build Tasker without installing it: + #+begin_src shell cabal build exe:tasker #+end_src -and copy the file to your bin. + +You can locate the built executable with: + +#+begin_src shell + cabal list-bin exe:tasker +#+end_src + +Then copy it manually to a directory on your =$PATH=, for example: + +#+begin_src shell + cp "$(cabal list-bin exe:tasker)" ~/.local/bin/tasker +#+end_src @@ -1,12 +1,105 @@ #!/usr/bin/env sh set -e +usage() { + echo "Usage: $0 [--shell fish|bash|none|all] [--no-man]" +} + +requested_shell= +install_man=true + +while [ "$#" -gt 0 ]; do + case "$1" in + --shell) + requested_shell=${2:-} + shift 2 + ;; + --shell=*) + requested_shell=${1#--shell=} + shift + ;; + --no-man) + install_man=false + shift + ;; + -h|--help) + usage + exit 0 + ;; + *) + echo "Unknown option: $1" >&2 + usage >&2 + exit 1 + ;; + esac +done + cabal build exe:tasker bin=$(cabal list-bin exe:tasker) -sudo install -Dm755 "$bin" /usr/local/bin/tasker -sudo install -Dm644 tasker.fish /usr/share/fish/vendor_completions.d/tasker.fish +sudo install -Dm755 "$bin" /usr/local/bin/tasker +echo "Installed $bin -> /usr/local/bin/tasker" + +detect_shell() { + if [ -n "$requested_shell" ]; then + echo "$requested_shell" + else + basename "${SHELL:-}" + fi +} + +install_fish_completion() { + if [ -f tasker.fish ]; then + sudo install -Dm644 tasker.fish \ + /usr/share/fish/vendor_completions.d/tasker.fish + echo "Installed tasker.fish -> /usr/share/fish/vendor_completions.d/tasker.fish" + else + echo "Skipped fish completion: tasker.fish not found" + fi +} + +install_bash_completion() { + if [ -f tasker.bash ]; then + sudo install -Dm644 tasker.bash \ + /usr/share/bash-completion/completions/tasker + echo "Installed tasker.bash -> /usr/share/bash-completion/completions/tasker" + else + echo "Skipped bash completion: tasker.bash not found" + fi +} + +install_manpage() { + if [ -f tasker.1 ]; then + sudo install -Dm644 tasker.1 \ + /usr/local/share/man/man1/tasker.1 + sudo mandb 2>/dev/null || true + echo "Installed tasker.1 -> /usr/local/share/man/man1/tasker.1" + else + echo "Skipped manpage: tasker.1 not found" + fi +} + +case "$(detect_shell)" in + fish) + install_fish_completion + ;; + bash) + install_bash_completion + ;; + all) + install_fish_completion + install_bash_completion + ;; + none) + echo "Skipped shell completions" + ;; + *) + echo "Unsupported shell: ${requested_shell:-${SHELL:-unset}}" + echo "Skipped shell completions" + ;; +esac -echo "Installed $bin -> /usr/local/bin/tasker" -echo "Installed tasker.fish -> /usr/share/fish/vendor_completions.d/tasker.fish" +if [ "$install_man" = true ]; then + install_manpage +fi diff --git a/tasker.1 b/tasker.1 new file mode 100644 index 0000000..7c7f90d --- /dev/null +++ b/tasker.1 @@ -0,0 +1,98 @@ +.TH TASKER 1 +.SH NAME +tasker \- manage local task directories +.SH SYNOPSIS +.B tasker +.RI COMMAND +.RI [ OPTIONS ] + +.SH DESCRIPTION +.B tasker +manages tasks stored as timestamp-named directories. + +Task directories are searched under +.B ./tasks +when that directory exists, otherwise under the current directory. + +.SH COMMANDS +.TP +.B new +Create a new task. +.TP +.B list +List tasks. +.TP +.B set +Update a task. +.TP +.B delete +Delete a task. + +.SH OPTIONS +.SS new +.TP +.BI \-p " PRIORITY" +Set priority as an integer. +.TP +.BI \-d " DESCRIPTION" +Set task description. + +.SS list +.TP +.BR \-s ", " \-\-status " " \fISTATUS\fR +Filter by status. One of: +.BR OPEN , +.BR IN_PROGRESS , +.BR CLOSED . +.TP +.BR \-p ", " \-\-priority " " \fIPRIORITY\fR +Filter by exact priority. +.TP +.BI \-\-min-priority " PRIORITY" +Filter by minimum priority. +.TP +.BR \-c ", " \-\-contains " " \fITEXT\fR +Filter by text. + +.SS set +.TP +.BI \-n ", " \-\-name " NAME" +Rename the task. +.TP +.BR \-s ", " \-\-status " " \fISTATUS\fR +Set task status. One of: +.BR OPEN , +.BR IN_PROGRESS , +.BR CLOSED . +.TP +.BR \-p ", " \-\-priority " " \fIPRIORITY\fR +Set task priority. +.TP +.BR \-d ", " \-\-desc " " \fIDESCRIPTION\fR +Set task description. + +.SH EXAMPLES +.TP +Create a task: +.B tasker new -p 3 -d "Write docs" +.TP +List open tasks: +.B tasker list --status OPEN +.TP +Update a task: +.B tasker set "24-01-30 12:00:00" --status CLOSED +.TP +Delete a task: +.B tasker delete "24-01-30 12:00:00" + +.SH FILES +.TP +.B ./tasks/ +Preferred task root when present. +.TP +.B . +Fallback task root. + +.SH SEE ALSO +.BR bash (1), +.BR fish (1) diff --git a/tasker.bash b/tasker.bash new file mode 100644 index 0000000..6b13ae8 --- /dev/null +++ b/tasker.bash @@ -0,0 +1,90 @@ +_tasker_task_dirs() { + local root d name + if [[ -d "$PWD/tasks" ]]; then + root="$PWD/tasks" + else + root="$PWD" + fi + + shopt -s nullglob + for d in "$root"/*/; do + name="${d%/}" + name="${name##*/}" + if [[ "$name" =~ ^[0-9]{2}-[0-9]{2}-[0-9]{2}\ [0-9]{2}:[0-9]{2}:[0-9]{2}(\.[0-9]+)?$ ]]; then + printf '%s\n' "$name" + fi + done +} + +_tasker_completion() { + local cur prev words cword subcommand + COMPREPLY=() + + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + + local commands="new list set delete" + + subcommand="" + for word in "${COMP_WORDS[@]:1}"; do + case "$word" in + new|list|set|delete) + subcommand="$word" + break + ;; + esac + done + + if [[ -z "$subcommand" ]]; then + COMPREPLY=( $(compgen -W "$commands" -- "$cur") ) + return 0 + fi + + case "$subcommand" in + new) + case "$prev" in + -p|-d) + return 0 + ;; + esac + COMPREPLY=( $(compgen -W "-p -d" -- "$cur") ) + ;; + + list) + case "$prev" in + -s|--status) + COMPREPLY=( $(compgen -W "OPEN IN_PROGRESS CLOSED" -- "$cur") ) + return 0 + ;; + -p|--priority|--min-priority|-c|--contains) + return 0 + ;; + esac + COMPREPLY=( $(compgen -W "-s --status -p --priority --min-priority -c --contains" -- "$cur") ) + ;; + + set) + case "$prev" in + -s|--status) + COMPREPLY=( $(compgen -W "OPEN IN_PROGRESS CLOSED" -- "$cur") ) + return 0 + ;; + -n|--name|-p|--priority|-d|--desc) + return 0 + ;; + esac + + if [[ "$cur" == -* ]]; then + COMPREPLY=( $(compgen -W "-n --name -s --status -p --priority -d --desc" -- "$cur") ) + else + COMPREPLY=( $(compgen -W "$(_tasker_task_dirs)" -- "$cur") ) + fi + ;; + + delete) + COMPREPLY=( $(compgen -W "$(_tasker_task_dirs)" -- "$cur") ) + ;; + esac +} + +complete -F _tasker_completion tasker |
