Compare commits

...

3 Commits

Author SHA1 Message Date
bcbd2a9077 feat: non-interactive imports 2025-06-15 21:42:45 +02:00
0b8066923b improvement: Calendar preview: weeks start on Mondays 2025-06-15 21:07:15 +02:00
06020740cc improvements: presentation
- collection label before time
- UTF8 arrow insead of `->`
2025-06-14 23:11:55 +02:00
6 changed files with 151 additions and 43 deletions

19
src/awk/calshift.awk Normal file
View File

@ -0,0 +1,19 @@
BEGIN {
ORS = ""
W3 = " "
W17 = W3 W3 W3 W3 W3 " "
}
NR == 1 { i++; print $0 "\n"; next }
NR == 2 { i++; print substr($0, 4, 17) " " substr($0, 1, 3) " \n"; next }
NR == 3 && /^ 1/ { print W17; }
NR == 3 && /^ / { print substr($0, 4, 17); next }
/[0-9]/ {
i++
print " " substr($0, 1, 3) " \n" substr($0, 4, 17)
}
END {
i++
print " " W3 " \n"
for (i; i<8; i++)
print " " W17 W3 " \n"
}

View File

@ -1,26 +1,25 @@
# 11:00|13:00|1748422800|1748430000|fpath|desc... # $s|$e|$starttime|$endtime|$fpath|$collection|$description
# 00:00|00:00|1748296800|1748383200|fpath|desc... function allday(collection, desc) {
function allday(desc) { return collection " " ITALIC FAINT " (allday) " OFF desc
return ITALIC FAINT " (allday) " OFF desc
} }
function endstoday(stop, desc) { function endstoday(stop, collection, desc) {
return CYAN " -- " stop OFF ": " desc return collection " " CYAN " -- " stop OFF ": " desc
} }
function slice(start, stop, desc) { function slice(start, stop, collection, desc) {
if (stop == "00:00") if (stop == "00:00")
return CYAN start " -- " OFF ": " desc return collection " " CYAN start " -- " OFF ": " desc
else else
return CYAN start OFF " -- " CYAN stop OFF ": " desc return collection " " CYAN start OFF " -- " CYAN stop OFF ": " desc
} }
function hrline(hour) { function hrline(hour) {
hour = hour < 10 ? "0"hour : hour hour = hour < 10 ? "0"hour : hour
print today, hour, "", "", "", FAINT hour ":00 ----------------------" OFF print today, hour, "", "", "", " " FAINT hour ":00 ----------------------" OFF
} }
function hrlines(start, stop, h, starth, stoph, tmp, i) { function hrlines(start, stop, h, starth, stoph, tmp, i) {
starth = substr(start, 1, 2) starth = substr(start, 1, 2)
stoph = substr(stop, 1, 2) stoph = substr(stop, 1, 2)
tmp = substr(start, 4, 2) == "00" ? 0 : 1 tmp = substr(start, 4, 2) == "00" ? 0 : 1
for (i=h; i < starth + tmp; i++) for (i=h; i < starth + tmp && i < dayend; i++)
hrline(i) hrline(i)
tmp = substr(stop, 4, 2) == "00" ? 0 : 1 tmp = substr(stop, 4, 2) == "00" ? 0 : 1
if (stoph + tmp < daystart) if (stoph + tmp < daystart)
@ -39,11 +38,11 @@ BEGIN {
OFF = "\033[m" OFF = "\033[m"
OFS = "|" OFS = "|"
} }
$1 == "00:00" && $2 == "00:00" { print today, $1, $3, $4, $5, allday($6); next } $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); next } $1 == "00:00" { print today, $1, $3, $4, $5, endstoday($2, $6, $7); next }
$1 ~ /^[0-9]{2}:[0-9]{2}$/ { $1 ~ /^[0-9]{2}:[0-9]{2}$/ {
daystart = hrlines($1, $2, daystart, starth, stoph, tmp, i) daystart = hrlines($1, $2, daystart, starth, stoph, tmp, i)
print today, $1, $3, $4, $5, slice($1, $2, $6) print today, $1, $3, $4, $5, slice($1, $2, $6, $7)
} }
END { END {
hrlines(dayend":00", 0, daystart, starth, stoph, tmp, i) hrlines(dayend":00", 0, daystart, starth, stoph, tmp, i)

16
src/awk/set.awk Normal file
View File

@ -0,0 +1,16 @@
function escape(str)
{
gsub("\\\\", "\\\\", str)
gsub(";", "\\\\;", str)
gsub(",", "\\\\,", str)
return str
}
BEGIN { FS = "[:;]"; }
/^BEGIN:VEVENT$/ { inside = 1 }
/^END:VEVENT$/ { inside = 0 }
$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 }
{ print }

View File

@ -12,7 +12,7 @@ BEGIN {
} }
/^[0-7] 00:00 -- 00:00/ { dayline = dayline " " c(); next } /^[0-7] 00:00 -- 00:00/ { dayline = dayline " " c(); next }
/^[0-7] 00:00 -- / { dayline = dayline " <--" $4 " " c(); next } /^[0-7] 00:00 -- / { dayline = dayline " <--" $4 " " c(); next }
/^[0-7] [0-9]{2}:[0-9]{2} -- 00:00/ { dayline = dayline " " $2 "--> " c(); next } /^[0-7] [0-9]{2}:[0-9]{2} -- 00:00/ { dayline = dayline " " $2 "" c(); next }
/^[0-7] [0-9]{2}:[0-9]{2} -- [0-9]{2}:[0-9]{2}/ { dayline = dayline " " $2 " - " $4 " " c(); next } /^[0-7] [0-9]{2}:[0-9]{2} -- [0-9]{2}:[0-9]{2}/ { dayline = dayline " " $2 " - " $4 " " c(); next }
/^[0-7]$/ && dayline { print "+", startofweek " +" $0-1 " days", "", dayline; } /^[0-7]$/ && dayline { print "+", startofweek " +" $0-1 " days", "", dayline; }
/^[0-7]$/ { /^[0-7]$/ {

View File

@ -17,6 +17,7 @@ if [ "${1:-}" = "--help" ]; then
echo " --day [date] Show appointments of specified day (today)" echo " --day [date] Show appointments of specified day (today)"
echo " --week [date] Show week of specified date (today)" echo " --week [date] Show week of specified date (today)"
echo " --import file Import iCalendar file" echo " --import file Import iCalendar file"
echo " --import-ni file Import iCalendar file non-interactively"
echo " --git cmd Run git command cmd relative to calendar root" echo " --git cmd Run git command cmd relative to calendar root"
echo " --git-init Enable the use of git" echo " --git-init Enable the use of git"
echo "" echo ""
@ -123,7 +124,7 @@ if [ "${1:-}" = "--preview-event" ]; then
start=$(datetime_str "$start" "%a ") start=$(datetime_str "$start" "%a ")
end=$(datetime_str "$end" "%a ") end=$(datetime_str "$end" "%a ")
location=$(awk -v field="LOCATION" "$AWK_GET" "$fpath") location=$(awk -v field="LOCATION" "$AWK_GET" "$fpath")
echo "📅 ${CYAN}$start${OFF} -> ${CYAN}$end${OFF}" echo "📅 ${CYAN}$start${OFF} ${CYAN}$end${OFF}"
if [ -n "${location:-}" ]; then if [ -n "${location:-}" ]; then
echo "📍 ${CYAN}$location${OFF}" echo "📍 ${CYAN}$location${OFF}"
fi fi
@ -137,7 +138,8 @@ fi
# Print preview of week. # Print preview of week.
# #
# @input $2: Line from week view # @input $2: Line from week view
# @req $AWK_CAL: Awk script to annotate calendar # @req $AWK_CALSHIFT: Awk script to make `cal` output to start on Mondays
# @req $AWK_CALANNOT: Awk script to annotate calendar
if [ "${1:-}" = "--preview-week" ]; then if [ "${1:-}" = "--preview-week" ]; then
sign=$(echo "$2" | cut -d '|' -f 1) sign=$(echo "$2" | cut -d '|' -f 1)
if [ "$sign" = "+" ]; then if [ "$sign" = "+" ]; then
@ -188,12 +190,12 @@ if [ "${1:-}" = "--preview-week" ]; then
fi fi
# show # show
( (
cal "$month_pre2" "$year_pre2" | awk -v cur="${var_pre2:-}" "$AWK_CAL" cal "$month_pre2" "$year_pre2" | awk "$AWK_CALSHIFT" | awk -v cur="${var_pre2:-}" "$AWK_CALANNOT"
cal "$month_pre" "$year_pre" | awk -v cur="${var_pre:-}" "$AWK_CAL" cal "$month_pre" "$year_pre" | awk "$AWK_CALSHIFT" | awk -v cur="${var_pre:-}" "$AWK_CALANNOT"
cal "$month" "$year" | awk -v cur="${var:-}" -v day="$day" "$AWK_CAL" cal "$month" "$year" | awk "$AWK_CALSHIFT" | awk -v cur="${var:-}" -v day="$day" "$AWK_CALANNOT"
cal "$month_nex" "$year_nex" | awk -v cur="${var_nex:-}" "$AWK_CAL" cal "$month_nex" "$year_nex" | awk "$AWK_CALSHIFT" | awk -v cur="${var_nex:-}" "$AWK_CALANNOT"
cal "$month_nex2" "$year_nex2" | awk -v cur="${var_nex2:-}" "$AWK_CAL" cal "$month_nex2" "$year_nex2" | awk "$AWK_CALSHIFT" | awk -v cur="${var_nex2:-}" "$AWK_CALANNOT"
cal "$month_nex3" "$year_nex3" | awk -v cur="${var_nex3:-}" "$AWK_CAL" cal "$month_nex3" "$year_nex3" | awk "$AWK_CALSHIFT" | awk -v cur="${var_nex3:-}" "$AWK_CALANNOT"
) | awk '{ l[NR%8] = l[NR%8] " " $0 } END {for (i in l) if (i>0) print l[i] }' ) | awk '{ l[NR%8] = l[NR%8] " " $0 } END {for (i in l) if (i>0) print l[i] }'
fi fi
exit exit
@ -240,6 +242,8 @@ __view_day() {
shift shift
fpath="$(echo "$1" | sed 's/|/ /g')" # we will use | as delimiter (need to convert back!) fpath="$(echo "$1" | sed 's/|/ /g')" # we will use | as delimiter (need to convert back!)
shift shift
collection="$1"
shift
description="$(echo "$*" | sed 's/|/:/g')" # we will use | as delimiter description="$(echo "$*" | sed 's/|/:/g')" # we will use | as delimiter
# #
daystart=$(date -d "$today 00:00:00" +"%s") daystart=$(date -d "$today 00:00:00" +"%s")
@ -259,7 +263,7 @@ __view_day() {
else else
continue continue
fi fi
echo "$s|$e|$starttime|$endtime|$fpath|$description" echo "$s|$e|$starttime|$endtime|$fpath|$collection|$description"
done) done)
fi fi
echo "$sef" | sort -n | awk -v today="$today" -v daystart="$DAY_START" -v dayend="$DAY_END" "$AWK_DAYVIEW" echo "$sef" | sort -n | awk -v today="$today" -v daystart="$DAY_START" -v dayend="$DAY_END" "$AWK_DAYVIEW"
@ -446,12 +450,14 @@ fi
### ###
### AWK scripts ### AWK scripts
### AWK_APPROX: Generate approximate data of all files ### AWK_APPROX: Generate approximate data of all files
### AWK_CAL: Annotate output of `cal` ### AWK_CALSHIFT: Shift calendar to start weeks on Mondays
### AWK_CALANNOT: Annotate calendar
### AWK_DAYVIEW: Generate view of the day ### AWK_DAYVIEW: Generate view of the day
### AWK_GET: Print field of iCalendar file ### AWK_GET: Print field of iCalendar file
### AWK_MERGE: Generate list of weeks with associated iCalendar files ### AWK_MERGE: Generate list of weeks with associated iCalendar files
### AWK_NEW: Make new iCalendar file ### AWK_NEW: Make new iCalendar file
### AWK_PARSE: Timezone aware parsing of iCalendar file for day view ### AWK_PARSE: Timezone aware parsing of iCalendar file for day view
### AWK_SET: Set value of specific field in iCalendar file
### AWK_UPDATE: Update iCalendar file ### AWK_UPDATE: Update iCalendar file
### AWK_WEEKVIEW: Generate view of the week ### AWK_WEEKVIEW: Generate view of the week
### ###
@ -505,9 +511,21 @@ AWK_NEW=$(
EOF EOF
) )
AWK_CAL=$( AWK_CALSHIFT=$(
cat <<'EOF' cat <<'EOF'
@@include src/awk/cal.awk @@include src/awk/calshift.awk
EOF
)
AWK_CALANNOT=$(
cat <<'EOF'
@@include src/awk/calannot.awk
EOF
)
AWK_SET=$(
cat <<'EOF'
@@include src/awk/set.awk
EOF EOF
) )
@ -602,6 +620,7 @@ __datetime_human_machine() {
### __edit ### __edit
### __new ### __new
### __delete ### __delete
### __import_to_collection
# __edit() # __edit()
# Edit iCalendar file. # Edit iCalendar file.
@ -752,13 +771,71 @@ __delete() {
done done
} }
# __import_to_collection()
# Import iCalendar file to specified collection. The only modification made to
# the file is setting the UID.
#
# @input $1: path to iCalendar file
# @input $2: collection name
# @req $ROOT: Path that contains the collections (see configuration)
# @req $UUIDGEN: `uuidgen` command
# @req $AWK_SET: Awk script to set field value
__import_to_collection() {
file="$1"
collection="$2"
fpath=""
while [ -f "$fpath" ] || [ -z "$fpath" ]; do
uuid=$($UUIDGEN)
fpath="$ROOT/$collection/$uuid.ics"
done
filetmp=$(mktemp)
awk -v field="UID" -v value="$uuid" "$AWK_SET" "$file" >"$filetmp"
mv "$filetmp" "$fpath"
if [ -n "${GIT:-}" ]; then
$GIT add "$fpath"
$GIT commit -m "Imported event" -- "$fpath"
fi
}
### ###
### Extra command-line options ### Extra command-line options
### --import-ni
### --import ### --import
### --git ### --git
### --git-init ### --git-init
### ###
# --import-ni
# Import iCalendar file noninteractively
#
# @input $2: Absolute path to iCalendar file
# @input $3: Collection
# @req $COLLECTION_LABELS: Mapping between collections and labels (see configuration)
# @req $ROOT: Path that contains the collections (see configuration)
# @return: On success, returns 0, otherwise 1
if [ "${1:-}" = "--import-ni" ]; then
shift
file="${1:-}"
collection="${2:-}"
if [ ! -f "$file" ]; then
err "File \"$file\" does not exist"
exit 1
fi
for c in $(echo "$COLLECTION_LABELS" | sed "s|=[^;]*;| |g"); do
if [ "$collection" = "$c" ]; then
cexists="yes"
break
fi
done
if [ -n "${cexists:-}" ] && [ -d "$ROOT/$collection" ]; then
__import_to_collection "$file" "$collection"
else
err "Collection \"$collection\" does not exist"
exit 1
fi
exit
fi
# --import # --import
# Import iCalendar file. # Import iCalendar file.
# #
@ -766,8 +843,6 @@ __delete() {
# @req $COLLECTION_LABELS: Mapping between collections and lables (see configuration) # @req $COLLECTION_LABELS: Mapping between collections and lables (see configuration)
# @req $AWK_PARSE: Parse awk script # @req $AWK_PARSE: Parse awk script
# @req $AWK_GET: Awk script to extract fields from iCalendar file # @req $AWK_GET: Awk script to extract fields from iCalendar file
# @req $ROOT: Path that contains the collections (see configuration)
# @req $UUIDGEN: `uuidgen` command
# @req $FZF: `fzf` command # @req $FZF: `fzf` command
# @req $CAT: Program to print # @req $CAT: Program to print
# @return: On success, returns 0, otherwise 1 # @return: On success, returns 0, otherwise 1
@ -816,16 +891,7 @@ if [ "${1:-}" = "--import" ]; then
if [ -z "$collection" ]; then if [ -z "$collection" ]; then
exit exit
fi fi
fpath="" __import_to_collection "$file" "$collection"
while [ -f "$fpath" ] || [ -z "$fpath" ]; do
uuid=$($UUIDGEN)
fpath="$ROOT/$collection/$uuid.ics"
done
cp -v "$file" "$fpath"
if [ -n "${GIT:-}" ]; then
$GIT add "$fpath"
$GIT commit -m "Imported event" -- "$fpath"
fi
break break
;; ;;
"no") "no")
@ -880,7 +946,7 @@ __refresh_data
### Exports ### Exports
# The preview calls run in subprocesses. These require the following variables: # The preview calls run in subprocesses. These require the following variables:
export ROOT CAT AWK_GET AWK_CAL CYAN WHITE ITALIC OFF export ROOT CAT AWK_GET AWK_CALSHIFT AWK_CALANNOT CYAN WHITE ITALIC OFF
# The reload commands also run in subprocesses, and use in addition # The reload commands also run in subprocesses, and use in addition
export COLLECTION_LABELS DAY_START DAY_END AWK_DAYVIEW AWK_WEEKVIEW AWK_PARSE 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 # as well as the following variables that will be dynamically specified. So, we
@ -995,13 +1061,19 @@ while true; do
)+transform( )+transform(
[ -n \"\${TZ:-}\" ] && echo \"change-list-label:\$WHITE\$ITALIC(\$TZ)\$OFF\" [ -n \"\${TZ:-}\" ] && echo \"change-list-label:\$WHITE\$ITALIC(\$TZ)\$OFF\"
)+transform( )+transform(
echo {} | grep \|\| || echo show-preview [ -n \"\$(echo {} | cut -d '|' -f 5)\" ] && echo show-preview
)" \ )" \
--bind="start:hide-preview" \ --bind="start:hide-preview" \
--bind="j:down+hide-preview+transform:echo {} | grep \|\| || echo show-preview" \ --bind="j:down+hide-preview+transform([ -n \"\$(echo {} | cut -d '|' -f 5)\" ] && echo show-preview)" \
--bind="k:up+hide-preview+transform:echo {} | grep \|\| || echo show-preview" \ --bind="k:up+hide-preview+transform([ -n \"\$(echo {} | cut -d '|' -f 5)\" ] && echo show-preview)" \
--bind="ctrl-j:down+hide-preview+transform([ -n \"\$(echo {} | cut -d '|' -f 5)\" ] && echo show-preview)" \
--bind="ctrl-k:up+hide-preview+transform([ -n \"\$(echo {} | cut -d '|' -f 5)\" ] && echo show-preview)" \
--bind="down:down+hide-preview+transform([ -n \"\$(echo {} | cut -d '|' -f 5)\" ] && echo show-preview)" \
--bind="up:up+hide-preview+transform([ -n \"\$(echo {} | cut -d '|' -f 5)\" ] && echo show-preview)" \
--bind="l:hide-preview+reload:$0 --reload-day {1} '+1 day'" \ --bind="l:hide-preview+reload:$0 --reload-day {1} '+1 day'" \
--bind="h: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-l:hide-preview+reload:$0 --reload-day {1} '+1 week'" \
--bind="ctrl-h: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-l:hide-preview+reload:$0 --reload-day {1} '+1 month'" \
@ -1074,6 +1146,8 @@ while true; do
--bind="k:up" \ --bind="k:up" \
--bind="l:unbind(load)+reload:$0 --reload-week {2} '+1 week'" \ --bind="l:unbind(load)+reload:$0 --reload-week {2} '+1 week'" \
--bind="h: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-l:unbind(load)+reload:$0 --reload-week {2} '+1 month'" \
--bind="ctrl-h: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-l:unbind(load)+reload:$0 --reload-week {2} '+1 year'" \