diff --git a/Makefile b/Makefile index 07e2f64..5da5863 100644 --- a/Makefile +++ b/Makefile @@ -7,11 +7,13 @@ PREFIX := /usr/local install: ./agenda.1.gz install -m 755 -D ./agenda $(PREFIX)/bin/agenda install -m 644 -D ./agenda.1.gz $(PREFIX)/share/man/man1/agenda.1.gz + install -m 644 -D ./completion.bash $(PREFIX)/share/bash-completion/completions/agenda .PHONY: uninstall uninstall: - rm $(PREFIX)/bin/agenda - rm $(PREFIX)/share/man/man1/agenda.1.gz + rm -f $(PREFIX)/bin/agenda + rm -f $(PREFIX)/share/man/man1/agenda.1.gz + rm -f $(PREFIX)/share/bash-completion/completions/agenda .PHONY: clean clean: diff --git a/completion.bash b/completion.bash new file mode 100644 index 0000000..0ab1957 --- /dev/null +++ b/completion.bash @@ -0,0 +1,190 @@ +#!/usr/bin/env bash + +# Bash completion script for the agenda(1) script. + +# TODO: Maybe its easier to add an api to the agenda script to query for the +# available -t option values? +if [ -z "$AGENDA_DIR" ]; then + if type xdg-user-dir >/dev/null 2>&1; then + AGENDA_DIR="$(xdg-user-dir DOCUMENTS)/agenda" + else + AGENDA_DIR="$HOME/.agenda" + fi +fi + +AGENDA_COMP_DATE_MOD=("next" "last") +AGENDA_COMP_DATE_UNIT=("day" "week" "month" "year") +AGENDA_COMP_DATE_DAY=("monday" "tuesday" "wednesday" "thursday" "friday" "saturday" "sunday") +AGENDA_COMP_DATE_REL=("yesterday" "today" "tomorrow") + +AGENDA_COMP_DATE=( + "${AGENDA_COMP_DATE_MOD[@]}" + "${AGENDA_COMP_DATE_UNIT[@]}" + "${AGENDA_COMP_DATE_DAY[@]}" + "${AGENDA_COMP_DATE_REL[@]}" +) + +__agenda_comp() { + # Pointer to current and last completion words. + local cur last + + cur=${COMP_WORDS[COMP_CWORD]} + last=${COMP_WORDS[COMP_CWORD - 1]} + + # All subcommands take a date (or parts of a date) as their final arguments. + if __agenda_comp_is_date_word "${COMP_WORDS[-2]}"; then + return + fi + + if __agenda_comp_is_date_mod "$last"; then + local words=("${AGENDA_COMP_DATE_UNIT[@]}" "${AGENDA_COMP_DATE_DAY[@]}") + mapfile -t COMPREPLY < <(compgen -W "${words[*]}" -- "$cur") + return + fi + + case ${COMP_WORDS[1]} in + "edit") + mapfile -t COMPREPLY < <(__agenda_comp_edit "$last" "$cur") + ;; + "list"|"ls") + mapfile -t COMPREPLY < <(__agenda_comp_list "$last" "$cur") + ;; + *) + if (( COMP_CWORD == 1 )); then + local dates + mapfile -t dates < <(__agenda_comp_iso_date "$cur") + + local words=( + "edit" + "list" + "${dates[@]}" + "${AGENDA_COMP_DATE[@]}" + "${AGENDA_COMP_DATE_DAY[@]}" + ) + + mapfile -t COMPREPLY < <(compgen -W "${words[*]}" -- "${COMP_WORDS[1]}") + else + # The "edit" subcommand is implicit. + mapfile -t COMPREPLY < <(__agenda_comp_edit "$last" "$cur") + fi + ;; + esac +} +complete -F __agenda_comp agenda + +__agenda_comp_edit() { + local last="$1" cur="$2" + + case $last in + "-"*"t") + compgen -W "$(__agenda_comp_names)" -- "$cur" + ;; + *) + __agenda_comp_opt "c" "$cur" + __agenda_comp_opt "E" "$cur" + __agenda_comp_opt "t" "$cur" + + compgen -W "${AGENDA_COMP_DATE[*]} $(__agenda_comp_iso_date "$cur")" -- "$cur" + ;; + esac +} + +__agenda_comp_list() { + local last="$1" cur="$2" + + case $last in + # TODO: Somehow complete date wrapped in a string? + "-"*"u") + local year + year=$(printf '%(%Y)T' -1) + compgen -W "$(__agenda_comp_iso_date "$cur" "$year")" -- "$cur" + ;; + "-"*"t") + compgen -W "$(__agenda_comp_names)" -- "$cur" + ;; + *) + __agenda_comp_opt "a" "$cur" + __agenda_comp_opt "c" "$cur" + __agenda_comp_opt "u" "$cur" + __agenda_comp_opt "t" "$cur" + + compgen -W "${AGENDA_COMP_DATE[*]} $(__agenda_comp_iso_date "$cur")" -- "$cur" + ;; + esac +} + +__agenda_comp_is_date_mod() { + [[ $last == "next" ]] || [[ $last == "last" ]] || [[ $last =~ ^[-+]?[0-9]+$ ]] +} + +__agenda_comp_is_date_word() { + local word words=( + "${AGENDA_COMP_DATE_DAY[@]}" + "${AGENDA_COMP_DATE_UNIT[@]}" + "${AGENDA_COMP_DATE_REL[@]}" + ) + for word in "${words[@]}"; do + if [[ $1 == "$word" ]]; then + return 0 + fi + done + + if [[ $1 =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}$ ]]; then + return 0 + fi + + return 1 +} + +__agenda_comp_names() { + local dir + for dir in "$AGENDA_DIR"/*; do + if [ -e "$dir" ]; then + echo "${dir##*/}" + fi + done +} + +# TODO: Complete years only without appending a space on completion (If +# possible, idk)? +__agenda_comp_iso_date() { + local year month day + + if [[ $1 =~ ^[0-9]{4}-? ]]; then + year=${1%%-*} + elif [[ -n ${2:-} ]]; then + year=$2 + else + return + fi + + for month in {1..12}; do + case $month in + 1|3|5|7|8|10|12) + for day in {1..31}; do + printf "%04d-%02d-%02d\n" "$year" "$month" "$day" + done + ;; + 4|6|9|11) + for day in {1..30}; do + printf "%04d-%02d-%02d\n" "$year" "$month" "$day" + done + ;; + 2) + # TODO: Leap years. + for day in {1..28}; do + printf "%04d-%02d-%02d\n" "$year" "$month" "$day" + done + ;; + esac + done +} + +__agenda_comp_opt() { + local regex="^.*[[:space:]]-.*$1.*.*$" + if ! [[ $COMP_LINE =~ $regex ]]; then + compgen -W "-$1" -- "$2" + fi +} + +# vi: noet sts=4 sw=4 tw=84