Files
fzf-vcal/src/main.sh

432 lines
14 KiB
Bash
Executable File

#!/bin/sh
set -eu
if [ "${1:-}" = "--help" ]; then
cat <<EOF
Usage: $0 [OPTION]
You may specify at most one of the following options:
--help Show this help and exit
--today Show today's appointments
--yesterday Show yesterday's appointments
--tomorrow Show tomorrow's appointments
--goto Interactively enter date to jump to
--new [date/date-time] Create new entry (today)
--day [date] Show appointments of specified day (today)
--week [date] Show week of specified date (today)
--import file Import iCalendar file
--import-ni file Import iCalendar file non-interactively
--git cmd Run git command cmd relative to calendar root
--git-init Enable the use of git
You may also start this program with setting locale and timezone information.
For instance, to see and modify all of your calendar entries from the
perspective of Saigon, run
TZ='Asia/Saigon' $0
Likewise, you may see your calendar in the Greek language with
LC_TIME=el_GR.UTF-8 $0
EOF
exit
fi
# Theme
. "sh/theme.sh"
# Misc helper functions
. "sh/misc.sh"
# Preview utilities
. "sh/preview.sh"
# Preview command-line options
. "sh/clipreview.sh"
# View utilities
. "sh/view.sh"
# Reloading command-line options
. "sh/clireload.sh"
# Configuration
. "sh/config.sh"
# Access to awk scripts
. "sh/awkscripts.sh"
# Functions to load calendar data
. "sh/load.sh"
# Functions to modify iCalendar files
. "sh/icalendar.sh"
# Extra, run-and-exit command-line options
. "sh/cliextra.sh"
### Start
__refresh_data
### Exports
# The preview calls run in subprocesses. These require the following variables:
export ROOT CAT AWK_GET AWK_CALSHIFT AWK_CALANNOT CYAN STRIKE FAINT WHITE ITALIC OFF AWK_ATTACHLS
# The reload commands also run in subprocesses, and use in addition
export COLLECTION_LABELS DAY_START DAY_END AWK_DAYVIEW AWK_WEEKVIEW AWK_PARSE
# as well as the following variables that will be dynamically specified. So, we
# export them in the main loop using the following function.
# __export()
# Re-export dynamical variables to subshells.
__export() {
DISPLAY_DATE=$(date -R -d "$DISPLAY_DATE")
export DISPLAY_DATE WEEKLY_DATA_FILE APPROX_DATA_FILE
if [ -n "${TZ:-}" ]; then
export TZ
fi
}
###
### Main loop with the command-line argument
### --today
### --yesterday
### --tomorrow
### --goto
### --new <optional date/date-time argument>
### --day <optional date/date-time argument>
### --week <optional date/date-time argument>
### --set-tz
###
### The command-line argument defaults to "--week today".
while true; do
export DISPLAY_DATE WEEKLY_DATA_FILE APPROX_DATA_FILE
case "${1:-}" in
--today | --yesterday | --tomorrow | --goto | --new | --day | --week | --set-tz) ;;
*)
DISPLAY_DATE="today"
set -- "--week" "$DISPLAY_DATE"
;;
esac
if [ "$1" = "--today" ]; then
DISPLAY_DATE="today"
set -- "--day" "$DISPLAY_DATE"
fi
if [ "$1" = "--yesterday" ]; then
DISPLAY_DATE="yesterday"
set -- "--day" "$DISPLAY_DATE"
fi
if [ "$1" = "--tomorrow" ]; then
DISPLAY_DATE="tomorrow"
set -- "--day" "$DISPLAY_DATE"
fi
if [ "$1" = "--goto" ]; then
DISPLAY_DATE=""
while [ -z "${DISPLAY_DATE:-}" ]; do
printf "Enter date you want to jump to, e.g., today + 1 month or 2024-1-14: " >/dev/tty
read -r tmp
if date -d "$tmp" >/dev/null; then
DISPLAY_DATE="$(date -d "$tmp" +"%D")"
fi
done
set -- "--day" "$DISPLAY_DATE"
fi
if [ "$1" = "--set-tz" ]; then
new_tz=$(find "$ZI_DIR" -type f | sed "s|^$ZI_DIR/*||" | $FZF)
if [ -n "$new_tz" ]; then
TZ="$new_tz"
__refresh_data
__export
fi
shift
fi
if [ "${1:-}" = "--new" ]; then
__new "${2:-}"
if [ -n "$start" ]; then
DISPLAY_DATE="$start"
else
DISPLAY_DATE="${2:-}"
fi
__refresh_data
__export
set -- "--day" "$DISPLAY_DATE"
fi
if [ "$1" = "--day" ]; then
DISPLAY_DATE="${2:-today}"
__export
selection=$(
__view_day |
$FZF \
--reverse \
--ansi \
--no-sort \
--no-input \
--margin='20%,5%' \
--border='double' \
--color=label:bold:green \
--border-label-pos=3 \
--list-border="top" \
--list-label-pos=3 \
--cycle \
--delimiter='\t' \
--with-nth='{6}' \
--accept-nth='1,2,3,4,5' \
--preview="$0 --preview-event {}" \
--expect="ctrl-n,ctrl-t,ctrl-g,ctrl-alt-d,esc,backspace,q,alt-v,x,c,a" \
--bind="load:pos(1)+transform(
echo change-border-label:🗓️ \$(date -d {1} +\"%A %e %B %Y\")
)+transform(
[ -n \"\${TZ:-}\" ] && echo \"change-list-label:\$WHITE\$ITALIC(\$TZ)\$OFF\"
)+transform(
[ -n \"\$(echo {} | cut -f 5)\" ] && echo show-preview
)" \
--bind="start:hide-preview" \
--bind="j:down+hide-preview+transform([ -n \"\$(echo {} | cut -f 5)\" ] && echo show-preview)" \
--bind="k:up+hide-preview+transform([ -n \"\$(echo {} | cut -f 5)\" ] && echo show-preview)" \
--bind="ctrl-j:down+hide-preview+transform([ -n \"\$(echo {} | cut -f 5)\" ] && echo show-preview)" \
--bind="ctrl-k:up+hide-preview+transform([ -n \"\$(echo {} | cut -f 5)\" ] && echo show-preview)" \
--bind="down:down+hide-preview+transform([ -n \"\$(echo {} | cut -f 5)\" ] && echo show-preview)" \
--bind="up:up+hide-preview+transform([ -n \"\$(echo {} | cut -f 5)\" ] && echo show-preview)" \
--bind="l:hide-preview+reload:$0 --reload-day {1} '+1 day'" \
--bind="h:hide-preview+reload:$0 --reload-day {1} '-1 day'" \
--bind="right:hide-preview+reload:$0 --reload-day {1} '+1 day'" \
--bind="left:hide-preview+reload:$0 --reload-day {1} '-1 day'" \
--bind="ctrl-l:hide-preview+reload:$0 --reload-day {1} '+1 week'" \
--bind="ctrl-h:hide-preview+reload:$0 --reload-day {1} '-1 week'" \
--bind="alt-l:hide-preview+reload:$0 --reload-day {1} '+1 month'" \
--bind="alt-h:hide-preview+reload:$0 --reload-day {1} '-1 month'" \
--bind="ctrl-r:hide-preview+reload:$0 --reload-day today" \
--bind="ctrl-s:execute($SYNC_CMD ; printf 'Press <enter> to continue.'; read -r tmp)" \
--bind="w:toggle-preview-wrap" \
--bind="ctrl-d:preview-down" \
--bind="ctrl-u:preview-up"
)
key=$(echo "$selection" | head -1)
line=$(echo "$selection" | tail -1)
if [ "$line" = "$key" ]; then
line=""
fi
DISPLAY_DATE=$(echo "$line" | cut -f 1)
hour=$(echo "$line" | cut -f 2)
start=$(echo "$line" | cut -f 3)
end=$(echo "$line" | cut -f 4)
fpath=$(echo "$line" | cut -f 5)
if [ "$key" = "ctrl-n" ]; then
if echo "$hour" | grep ":"; then
hour="$DAY_START"
fi
set -- "--new" "$DISPLAY_DATE $hour:00"
elif [ "$key" = "ctrl-alt-d" ] && [ -n "$fpath" ]; then
__delete "$fpath"
__refresh_data
set -- "--day" "$DISPLAY_DATE"
elif [ "$key" = "ctrl-g" ]; then
set -- "--goto"
elif [ "$key" = "ctrl-t" ]; then
set -- "--set-tz" "--day" "$DISPLAY_DATE"
elif [ "$key" = "esc" ] || [ "$key" = "backspace" ] || [ "$key" = "q" ]; then
set -- "--week" "$DISPLAY_DATE"
elif [ "$key" = "alt-v" ] && [ -f "$ROOT/$fpath" ]; then
$EDITOR "$ROOT/$fpath"
elif [ "$key" = "x" ] && [ -f "$ROOT/$fpath" ]; then
__cancel_toggle "$fpath"
elif [ "$key" = "c" ] && [ -f "$ROOT/$fpath" ]; then
__tentative_toggle "$fpath"
elif [ "$key" = "a" ] && [ -f "$ROOT/$fpath" ]; then
att=$(
awk "$AWK_ATTACHLS" "$ROOT/$fpath" |
$FZF \
--delimiter="\t" \
--accept-nth=1,2,3,4 \
--with-nth="Attachment {1}: \"{2}\" {3} ({5})" \
--no-sort \
--tac \
--margin="30%,30%" \
--border=bold \
--border-label="Attachment View Keys: <enter> open, <ctrl-alt-d> delete, <shift-a> add" \
--expect="A" \
--expect="ctrl-c,ctrl-g,ctrl-q,ctrl-d,esc,q,backspace" \
--print-query \
--bind="start:hide-input" \
--bind="ctrl-alt-d:show-input+change-query(ctrl-alt-d)+accept" \
--bind="load:transform:[ \"\$FZF_TOTAL_COUNT\" -eq 0 ] && echo 'unbind(enter)+unbind(ctrl-alt-d)'" \
--bind="w:toggle-wrap" \
--bind="j:down" \
--bind="k:up" ||
true
)
key=$(echo "$att" | head -2 | xargs)
sel=$(echo "$att" | tail -1)
if [ "$key" = "ctrl-c" ] ||
[ "$key" = "ctrl-g" ] ||
[ "$key" = "ctrl-q" ] ||
[ "$key" = "ctrl-d" ] ||
[ "$key" = "esc" ] ||
[ "$key" = "q" ] ||
[ "$key" = "backspace" ]; then
continue
fi
if [ "$key" = "A" ]; then
__add_attachment "$fpath"
__refresh_data
continue
fi
attid=$(echo "$sel" | cut -f 1)
attname=$(echo "$sel" | cut -f 2)
attfmt=$(echo "$sel" | cut -f 3)
attenc=$(echo "$sel" | cut -f 4)
if [ -z "$attid" ]; then
# This line should be unreachable
continue
fi
if [ "$key" = "ctrl-alt-d" ]; then
while true; do
printf "Are you sure you want to delete attachment \"%s\"? (yes/no): " "$attid" >/dev/tty
read -r yn
case $yn in
"yes")
filetmp=$(mktemp)
awk -v id="$attid" "$AWK_ATTACHRM" "$ROOT/$fpath" >"$filetmp"
mv "$filetmp" "$ROOT/$fpath"
if [ -n "${GIT:-}" ]; then
$GIT add "$fpath"
$GIT commit -q -m "Deleted attachment from event '$(__summary_for_commit "$fpath") ...'" -- "$fpath"
fi
__refresh_data
break
;;
"no")
break
;;
*)
echo "Please answer \"yes\" or \"no\"." >/dev/tty
;;
esac
done
continue
fi
if [ "$attenc" != "base64" ]; then
err "Unsupported attachment encoding: $attenc"
read -r tmp
continue
fi
if [ -n "$attname" ]; then
tmpdir=$(mktemp -d)
attpath="$tmpdir/$attname"
elif [ -n "$attfmt" ]; then
attext=$(echo "$attfmt" | cut -d "/" -f 2)
attpath=$(mktemp --suffix="$attext")
else
attpath=$(mktemp)
fi
# Get file and uncode
awk -v id="$attid" "$AWK_ATTACHDD" "$ROOT/$fpath" | base64 -d >"$attpath"
fn=$(file "$attpath")
while true; do
printf "Are you sure you want to open \"%s\"? (yes/no): " "$fn" >/dev/tty
read -r yn
case $yn in
"yes")
$OPEN "$attpath"
printf "Press <enter> to continue." >/dev/tty
read -r tmp
break
;;
"no")
break
;;
*)
echo "Please answer \"yes\" or \"no\"." >/dev/tty
;;
esac
done
# Clean up
rm -f "$attpath"
if [ -n "${tmpdir:-}" ] && [ -d "${tmpdir:-}" ]; then
rm -rf "$tmpdir"
fi
elif [ -z "$key" ] && [ -n "$fpath" ]; then
__edit "$start" "$end" "$fpath"
set -- "--day" "$DISPLAY_DATE"
fi
__export
fi
if [ "${1:-}" = "--week" ]; then
DISPLAY_DATE="${2:-today}"
DISPLAY_POS=$((8 - $(date -d "$DISPLAY_DATE" +"%u")))
__export
selection=$(
__view_week |
$FZF \
--tac \
--no-sort \
--no-hscroll \
--ellipsis="" \
--delimiter="\t" \
--with-nth="{4}" \
--accept-nth=1,2 \
--ansi \
--gap 1 \
--no-scrollbar \
--no-input \
--info=right \
--margin="1" \
--info-command="printf \"$(date +"%R %Z")\"; [ -n \"\${TZ:-}\" ] && printf \" (\$TZ)\"" \
--preview-window=up,7,border-bottom \
--preview="$0 --preview-week {}" \
--bind="load:pos($DISPLAY_POS)" \
--expect="ctrl-n,ctrl-g,ctrl-t" \
--bind="q:abort" \
--bind="j:down" \
--bind="k:up" \
--bind="l:unbind(load)+reload:$0 --reload-week {2} '+1 week'" \
--bind="h:unbind(load)+reload:$0 --reload-week {2} '-1 week'" \
--bind="right:unbind(load)+reload:$0 --reload-week {2} '+1 week'" \
--bind="left:unbind(load)+reload:$0 --reload-week {2} '-1 week'" \
--bind="ctrl-l:unbind(load)+reload:$0 --reload-week {2} '+1 month'" \
--bind="ctrl-h:unbind(load)+reload:$0 --reload-week {2} '-1 month'" \
--bind="alt-l:unbind(load)+reload:$0 --reload-week {2} '+1 year'" \
--bind="alt-h:unbind(load)+reload:$0 --reload-week {2} '-1 year'" \
--bind="ctrl-r:rebind(load)+reload($0 --reload-week today)+show-preview" \
--bind="ctrl-s:execute($SYNC_CMD ; printf 'Press <enter> to continue.'; read -r tmp)" \
--bind="/:show-input+unbind(q)+unbind(j)+unbind(k)+unbind(l)+unbind(h)+unbind(ctrl-l)+unbind(ctrl-h)+unbind(alt-l)+unbind(alt-h)+unbind(load)+hide-preview+reload:$0 --reload-all" \
--bind="backward-eof:hide-input+rebind(q)+rebind(j)+rebind(k)+rebind(l)+rebind(h)+rebind(ctrl-l)+rebind(ctrl-h)+rebind(alt-l)+rebind(alt-h)+rebind(load)+show-preview+reload:$0 --reload-week today" \
--bind="esc:clear-query+hide-input+rebind(q)+rebind(j)+rebind(k)+rebind(l)+rebind(h)+rebind(ctrl-l)+rebind(ctrl-h)+rebind(alt-l)+rebind(alt-h)+rebind(load)+show-preview+reload:$0 --reload-week today"
)
key=$(echo "$selection" | head -1)
line=$(echo "$selection" | tail -1)
if [ "$line" = "$key" ]; then
line=""
fi
sign=$(echo "$line" | cut -f 1)
DISPLAY_DATE=$(echo "$line" | cut -f 2)
if [ "$key" = "ctrl-n" ]; then
if [ "$sign" = "~" ]; then
DISPLAY_DATE=""
fi
set -- "--new" "${DISPLAY_DATE:-today} $DAY_START:00"
elif [ "$key" = "ctrl-g" ]; then
set -- "--goto"
elif [ "$key" = "ctrl-t" ]; then
set -- "--set-tz" "$*"
else
if [ "$sign" = "~" ]; then
set -- "--week" "$DISPLAY_DATE"
else
set -- "--day" "$DISPLAY_DATE"
fi
fi
__export
fi
done