From 1093bc15e57e9a06ae6b66780a1140574bbfabfd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=84min=20Baumeler?= Date: Mon, 16 Jun 2025 15:07:58 +0200 Subject: [PATCH] feat: STATUS support --- README.md | 4 ++- src/awk/dayview.awk | 38 +++++++++++++++------ src/awk/new.awk | 2 +- src/awk/parse.awk | 6 ++-- src/awk/set.awk | 13 +++++-- src/main.sh | 82 +++++++++++++++++++++++++++++++++++++++++---- 6 files changed, 123 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 73034ad..8889251 100644 --- a/README.md +++ b/README.md @@ -106,7 +106,9 @@ Here is the list of all available keybindings: | `ctrl-t` | set timezon | | `ctrl-s` | synchronize | | `ctrl-n` | add new entry | -| `\` | search all appointment s| +| `\` | search all appointment s | +| `x` | Cancel and confirm entry | +| `c` | Unconfirm and confirm entry | ### Day view diff --git a/src/awk/dayview.awk b/src/awk/dayview.awk index 3326180..7285169 100644 --- a/src/awk/dayview.awk +++ b/src/awk/dayview.awk @@ -10,37 +10,54 @@ # Functions +# Set event color based on status + +# @input status: Event status, one of TENTATIVE, CONFIRMED, CANCELLED +# @return: Color modifier +function color_from_status(status) { + return status == "CANCELLED" ? STRIKE CYAN : status == "TENTATIVE" ? FAINT CYAN : CYAN +} + # Return line for all-day event. # +# @local variables: color # @input collection: Collection symbol # @input desc: Event description +# @input status: Event status, one of TENTATIVE, CONFIRMED, CANCELLED # @return: Single-line string -function allday(collection, desc) { - return collection " " LIGHT_CYAN ITALIC FAINT " (allday) " OFF CYAN desc OFF +function allday(collection, desc, status, color) { + color = color_from_status(status) + return collection " " LIGHT_CYAN ITALIC FAINT " (allday) " OFF color desc OFF } # Return line for multi-day event, or event that starts at midnight, which ends today. # +# @local variables: color # @input stop: Time at which the event ends # @input collection: Collection symbol # @input desc: Event description +# @input status: Event status, one of TENTATIVE, CONFIRMED, CANCELLED # @return: Single-line string -function endstoday(stop, collection, desc) { - return collection " " LIGHT_CYAN " -- " stop OFF ": " CYAN desc OFF +function endstoday(stop, collection, desc, status) { + color = color_from_status(status) + return collection " " LIGHT_CYAN " -- " stop OFF ": " color desc OFF } # Return line for event that starts sometime today. # +# @local variables: color # @input start: Time at which the event starts # @input stop: Time at which the event ends # @input collection: Collection symbol # @input desc: Event description +# @input status: Event status, one of TENTATIVE, CONFIRMED, CANCELLED # @return: Single-line string -function slice(start, stop, collection, desc) { +function slice(start, stop, collection, desc, status) { + color = color_from_status(status) if (stop == "00:00") - return collection " " LIGHT_CYAN start " -- " OFF ": " CYAN desc OFF + return collection " " LIGHT_CYAN start " -- " OFF ": " color desc OFF else - return collection " " LIGHT_CYAN start OFF " -- " LIGHT_CYAN stop OFF ": " CYAN desc OFF + return collection " " LIGHT_CYAN start OFF " -- " LIGHT_CYAN stop OFF ": " color desc OFF } # Print line for a single hour entry. @@ -79,13 +96,14 @@ BEGIN { CYAN = "\033[1;36m" ITALIC = "\033[3m" FAINT = "\033[2m" + STRIKE = "\033[9m" OFF = "\033[m" OFS = "|" } -$1 == "00:00" && $2 == "00:00" { print today, $1, $3, $4, $5, allday($6, $7); next } -$1 == "00:00" { print today, $1, $3, $4, $5, endstoday($2, $6, $7); next } +$1 == "00:00" && $2 == "00:00" { print today, $1, $3, $4, $5, allday($6, $7, $8); next } +$1 == "00:00" { print today, $1, $3, $4, $5, endstoday($2, $6, $7, $8); next } $1 ~ /^[0-9]{2}:[0-9]{2}$/ { daystart = hrlines($1, $2, daystart) - print today, $1, $3, $4, $5, slice($1, $2, $6, $7) + print today, $1, $3, $4, $5, slice($1, $2, $6, $7, $8) } END { hrlines(dayend":00", 0, daystart) } diff --git a/src/awk/new.awk b/src/awk/new.awk index 94ebe2f..4e6c027 100644 --- a/src/awk/new.awk +++ b/src/awk/new.awk @@ -129,7 +129,7 @@ END { print "CREATED:" zulu print "SEQUENCE:1" print "LAST-MODIFIED:" zulu - print "STATUS:VEVENT" + print "STATUS:CONFIRMED" print "DTSTART;VALUE=" from_type ":" from print "DTEND;VALUE=" to_type ":" to if (summary) print_fold("SUMMARY:", summary) diff --git a/src/awk/parse.awk b/src/awk/parse.awk index 4008922..b6a5577 100644 --- a/src/awk/parse.awk +++ b/src/awk/parse.awk @@ -1,7 +1,7 @@ ## src/awk/parse.awk ## Parse iCalendar file and print its key aspects: ## ``` -## +## ## ```. ## ## @assign collection_labels: See configuration of the current program. @@ -72,7 +72,8 @@ function print_data(start, dur, end, summary, cmd, collection, depth, path) { cmd = "date -d '" end "' +\"%s\"" cmd | getline end close(cmd) - print start, end, fpath, collection, summary + status = status ? status : "CONFIRMED" + print start, end, fpath, collection, status, summary } # AWK program @@ -89,6 +90,7 @@ BEGIN { /^DTSTART/ && inside { start = parse() } /^DTEND/ && inside { end = parse() } /^DURATION/ && inside { end = parse_duration($NF); dur = 1 } +/^STATUS/ && inside { status = $NF } /^[^ ]/ && rs { rs = 0 } /^ / && rs { summary = summary substr($0, 2) } /^SUMMARY/ && inside { rs = 1; summary = $0 } diff --git a/src/awk/set.awk b/src/awk/set.awk index b954a58..66e8306 100644 --- a/src/awk/set.awk +++ b/src/awk/set.awk @@ -22,12 +22,21 @@ function escape(str) # AWK program -BEGIN { FS = "[:;]" } +BEGIN { FS = "[:;]"; zulu = strftime("%Y%m%dT%H%M%SZ", systime(), 1) } /^BEGIN:VEVENT$/ { inside = 1 } -/^END:VEVENT$/ { inside = 0; if (!duplic) print field ":" escape(value) } +/^END:VEVENT$/ { + inside = 0 + if (!duplic) + print field ":" escape(value) + seq = seq ? seq + 1 : 1 + print "SEQUENCE:" seq + print "LAST-MODIFIED:" zulu +} $1 == field && inside { con = 1; duplic = 1; print field ":" escape(value); next } $1 == field && duplic { con = 1; next } /^ / && con { next } /^ / && con { next } /^[^ ]/ && con { con = 0 } +/^SEQUENCE/ && inside { seq = $2; next } # store sequence number and skip +/^LAST-MODIFIED/ && inside { next } { print } diff --git a/src/main.sh b/src/main.sh index 54842ad..43d4be5 100755 --- a/src/main.sh +++ b/src/main.sh @@ -124,7 +124,13 @@ if [ "${1:-}" = "--preview-event" ]; then start=$(datetime_str "$start" "%a ") end=$(datetime_str "$end" "%a ") location=$(awk -v field="LOCATION" "$AWK_GET" "$fpath") - echo "📅 ${CYAN}$start${OFF} → ${CYAN}$end${OFF}" + status=$(awk -v field="STATUS" "$AWK_GET" "$fpath") + if [ "$status" = "TENTATIVE" ]; then + symb="🟡" + elif [ "$status" = "CANCELLED" ]; then + symb="❌" + fi + echo "📅${symb:-} ${CYAN}$start${OFF} → ${CYAN}$end${OFF}" if [ -n "${location:-}" ]; then echo "📍 ${CYAN}$location${OFF}" fi @@ -244,6 +250,8 @@ __view_day() { shift collection="$1" shift + status="$1" + shift description="$(echo "$*" | sed 's/|/:/g')" # we will use | as delimiter # daystart=$(date -d "$today 00:00:00" +"%s") @@ -263,7 +271,7 @@ __view_day() { else continue fi - echo "$s|$e|$starttime|$endtime|$fpath|$collection|$description" + echo "$s|$e|$starttime|$endtime|$fpath|$collection|$description|$status" done) fi echo "$sef" | sort -n | awk -v today="$today" -v daystart="$DAY_START" -v dayend="$DAY_END" "$AWK_DAYVIEW" @@ -277,6 +285,7 @@ __view_day() { # @req $ROOT: Path that contains the collections (see configuration) # @req $COLLECTION_LABELS: Mapping between collections and lables (see configuration) # @req $AWK_WEEKVIEW: Week-view awk script +# @req colors __view_week() { weeknr=$(date -d "$DISPLAY_DATE" +"%G.%V") files=$(grep "^$weeknr\ " "$WEEKLY_DATA_FILE" | cut -d " " -f 2-) @@ -302,7 +311,16 @@ __view_week() { shift #fpath="$1" shift - description="$*" + collection="$1" + shift + status="$1" + shift + if [ "$status" = "TENTATIVE" ]; then + symb="$FAINT$CYAN" + elif [ "$status" = "CANCELLED" ]; then + symb="$STRIKE" + fi + description="${symb:-}$*$OFF" for i in $(seq 0 7); do daystart=$(date -d "$startofweek +$i days 00:00:00" +"%s") dayend=$(date -d "$startofweek +$i days 23:59:59" +"%s") @@ -536,8 +554,9 @@ EOF #RED="\033[1;31m" WHITE="\033[1;97m" CYAN="\033[1;36m" +STRIKE="\033[9m" ITALIC="\033[3m" -#FAINT="\033[2m" +FAINT="\033[2m" OFF="\033[m" ### @@ -631,6 +650,8 @@ __summary_for_commit() { ### __new ### __delete ### __import_to_collection +### __cancel_toggle +### __tentative_toggle # __edit() # Edit iCalendar file. @@ -807,6 +828,51 @@ __import_to_collection() { fi } +# __cancel_toggle() +# Set status of appointment to CANCELLED or CONFIRMED (toggle) +# +# @input $1: path to iCalendar file +# @req $ROOT: Path that contains the collections (see configuration) +# @req $AWK_SET: Awk script to set field value +# @req $AWK_GET: Awk script to extract fields from iCalendar file +__cancel_toggle() { + fpath="$ROOT/$1" + status=$(awk -v field="STATUS" "$AWK_GET" "$fpath") + newstatus="CANCELLED" + if [ "${status:-}" = "$newstatus" ]; then + newstatus="CONFIRMED" + fi + filetmp=$(mktemp) + awk -v field="STATUS" -v value="$newstatus" "$AWK_SET" "$fpath" >"$filetmp" + mv "$filetmp" "$fpath" + if [ -n "${GIT:-}" ]; then + $GIT add "$fpath" + $GIT commit -m "Event '$(__summary_for_commit "$fpath") ...' has now status $status" -- "$fpath" + fi +} + +# __tentative_toggle +# Toggle status flag: CONFIRMED <-> TENTATIVE +# @input $1: path to iCalendar file +# @req $ROOT: Path that contains the collections (see configuration) +# @req $AWK_SET: Awk script to set field value +# @req $AWK_GET: Awk script to extract fields from iCalendar file +__tentative_toggle() { + fpath="$ROOT/$1" + status=$(awk -v field="STATUS" "$AWK_GET" "$fpath") + newstatus="TENTATIVE" + if [ "${status:-}" = "$newstatus" ]; then + newstatus="CONFIRMED" + fi + filetmp=$(mktemp) + awk -v field="STATUS" -v value="$newstatus" "$AWK_SET" "$fpath" >"$filetmp" + mv "$filetmp" "$fpath" + if [ -n "${GIT:-}" ]; then + $GIT add "$fpath" + $GIT commit -m "Event '$(__summary_for_commit "$fpath") ...' has now status $status" -- "$fpath" + fi +} + ### ### Extra command-line options ### --import-ni @@ -956,7 +1022,7 @@ __refresh_data ### Exports # The preview calls run in subprocesses. These require the following variables: -export ROOT CAT AWK_GET AWK_CALSHIFT AWK_CALANNOT CYAN WHITE ITALIC OFF +export ROOT CAT AWK_GET AWK_CALSHIFT AWK_CALANNOT CYAN STRIKE FAINT WHITE ITALIC OFF # 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 @@ -1065,7 +1131,7 @@ while true; do --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" \ + --expect="ctrl-n,ctrl-t,ctrl-g,ctrl-alt-d,esc,backspace,q,alt-v,x,c" \ --bind="load:pos(1)+transform( echo change-border-label:🗓️ \$(date -d {1} +\"%A %e %B %Y\") )+transform( @@ -1121,6 +1187,10 @@ while true; do 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 [ -z "$key" ] && [ -n "$fpath" ]; then __edit "$start" "$end" "$fpath" set -- "--day" "$DISPLAY_DATE"