#!/bin/zsh # All arguments are joined with spaces and inserted into the calendar # file at the appropriate point. # # While the function compares the date of the new entry with dates in the # existing calendar file, it does not do any sorting; it inserts the new # entry before the first existing entry with a later date and time. emulate -L zsh setopt extendedglob local calendar newfile REPLY lastline opt local -a calendar_entries lockfiles reply integer my_date done rstat nolock nobackup new_recurring old_recurring local -A reply parse_new parse_old recurring_uids autoload -U calendar_{parse,read,lockfiles} while getopts "BL" opt; do case $opt in (B) nobackup=1 ;; (L) nolock=1 ;; (*) return 1 ;; esac done shift $(( OPTIND - 1 )) # Read the calendar file from the calendar-file style zstyle -s ':datetime:calendar_add:' calendar-file calendar || calendar=~/calendar newfile=$calendar.new.$HOST.$$ if ! calendar_parse "$*"; then print "$0: failed to parse date/time" >&2 return 1 fi parse_new=("${(@kv)reply}") (( my_date = $parse_new[time] )) [[ -n $parse_new[rpttime] ]] && (( new_recurring = 1 )) # $calendar doesn't necessarily exist yet. local -a match mbegin mend local my_uid their_uid # Match a UID, a unique identifier for the entry inherited from # text/calendar format. local uidpat='(|*[[:space:]])UID[[:space:]]##(#b)([[:xdigit:]]##)(|[[:space:]]*)' if [[ "$*" = ${~uidpat} ]]; then my_uid=${(U)match[1]} fi # start of block for following always to clear up lockfiles. { (( nolock )) || calendar_lockfiles $calendar || return 1 if [[ -f $calendar ]]; then calendar_read $calendar if [[ -n $my_uid ]]; then # Pre-scan to find recurring events with a UID for line in $calendar_entries; do calendar_parse $line || continue # Recurring with a UID? if [[ -n $reply[rpttime] && $line = ${~uidpat} ]]; then # Yes, so record this as a recurring event. their_uid=${(U)match[1]} recurring_uids[$their_uid]=$reply[time] fi done fi { for line in $calendar_entries; do calendar_parse $line || continue parse_old=("${(@kv)reply}") if (( ! done && ${parse_old[time]} > my_date )); then print -r -- "$*" (( done = 1 )) fi if [[ -n $parse_old[rpttime] ]]; then (( old_recurring = 1 )) else (( old_recurring = 0 )) fi if [[ -n $my_uid && $line = ${~uidpat} ]]; then their_uid=${(U)match[1]} if [[ $my_uid = $their_uid ]]; then # Deal with recurrences, being careful in case there # are one-off variants that don't replace recurrences. # # Bug 1: "calendar" still doesn't know about one-off variants. # Bug 2: neither do I; how do we know which occurrence # it replaces? # Bug 3: the code for calculating recurrences is awful anyway. if (( old_recurring && new_recurring )); then # Replacing a recurrence; there can be only one. continue elif (( ! new_recurring )); then # Not recurring. See if we have previously found # a recurrent version [[ -n $recurring_uids[$their_uid] ]] && (( old_recurring = 1 )) # No, so assume this is a straightforward replacement # of a non-recurring event. (( ! old_recurring )) && continue # It's recurring, but if this is a one-off at the # same time as the previous one, replace anyway. [[ -z $parse_old[$rpttime] ]] && (( ${parse_new[time]} == ${parse_old[time]} )) && continue fi fi fi if [[ $REPLY -eq $my_date && $line = "$*" ]]; then (( done )) && continue # paranoia: shouldn't happen (( done = 1 )) fi print -r -- $line done (( done )) || print -r -- "$*" } >$newfile if (( ! nobackup )); then if ! mv $calendar $calendar.old; then print "Couldn't back up $calendar to $calendar.old. New calendar left in $newfile." >&2 (( rstat = 1 )) fi fi else print -r -- $line >$newfile fi if (( !rstat )) && ! mv $newfile $calendar; then print "Failed to rename $newfile to $calendar. Old calendar left in $calendar.old." >&2 (( rstat = 1 )) fi } always { (( ${#lockfiles} )) && rm -f $lockfiles } return $rstat