diff options
Diffstat (limited to 'Functions/Calendar')
-rw-r--r-- | Functions/Calendar/.distfiles | 23 | ||||
-rw-r--r-- | Functions/Calendar/calendar | 45 | ||||
-rw-r--r-- | Functions/Calendar/calendar_add | 62 | ||||
-rw-r--r-- | Functions/Calendar/calendar_parse | 83 |
4 files changed, 169 insertions, 44 deletions
diff --git a/Functions/Calendar/.distfiles b/Functions/Calendar/.distfiles index c49469431..7c14c384a 100644 --- a/Functions/Calendar/.distfiles +++ b/Functions/Calendar/.distfiles @@ -1,13 +1,14 @@ DISTFILES_SRC=' - .distfiles - age - calendar - calendar_add - calendar_edit - calendar_lockfiles - calendar_read - calendar_scandate - calendar_show - calendar_showdate - calendar_sort +.distfiles +age +calendar +calendar_add +calendar_edit +calendar_lockfiles +calendar_parse +calendar_read +calendar_scandate +calendar_show +calendar_showdate +calendar_sort ' diff --git a/Functions/Calendar/calendar b/Functions/Calendar/calendar index 7dec84e28..246cf2383 100644 --- a/Functions/Calendar/calendar +++ b/Functions/Calendar/calendar @@ -1,18 +1,19 @@ emulate -L zsh setopt extendedglob -local line showline restline REPLY REPLY2 userange pruned nobackup datefmt +local line showline restline REPLY REPLY2 userange nobackup datefmt local calendar donefile sched newfile warnstr mywarnstr newdate integer time start stop today ndays y m d next=-1 shown done nodone integer verbose warntime mywarntime t tcalc tsched i rstat remaining integer showcount icount repeating repeattime resched showall brief local -a calendar_entries calendar_addlines local -a times calopts showprog lockfiles match mbegin mend +local -A reply zmodload -i zsh/datetime || return 1 zmodload -i zsh/zutil || return 1 -autoload -U calendar_{add,read,scandate,show,lockfiles} +autoload -U calendar_{add,parse,read,scandate,show,lockfiles} # Read the calendar file from the calendar-file style zstyle -s ':datetime:calendar:' calendar-file calendar || calendar=~/calendar @@ -254,31 +255,29 @@ fi calendar_read $calendar for line in $calendar_entries; do - # This call sets REPLY to the date and time in seconds since the epoch, - # REPLY2 to the line with the date and time removed. - calendar_scandate -as $line || continue - (( t = REPLY )) - restline=$REPLY2 - + calendar_parse $line || continue + + # Extract returned parameters from $reply + # Time of event + (( t = ${reply[time]} )) + # Remainder of line including RPT and WARN stuff: we need + # to keep these for rescheduling. + restline=$reply[text1] # Look for specific warn time. - pruned=${restline#(|*[[:space:],])WARN[[:space:]]} - (( mywarntime = warntime )) - mywarnstr=$warnstr - if [[ $pruned != $restline ]]; then - if calendar_scandate -asm -R $t $pruned; then - (( mywarntime = t - REPLY )) - mywarnstr=${pruned%%"$REPLY2"} - fi + if [[ -n ${reply[warntime]} ]]; then + (( mywarntime = t - ${reply[warntime]} )) + mywarnstr=${reply[warnstr]} + else + (( mywarntime = warntime )) + mywarnstr=$warnstr fi - # Look for a repeat time. - (( repeating = 0 )) - pruned=${restline#(|*[[:space:],])RPT[[:space:]]} - if [[ $pruned != $restline ]]; then - if calendar_scandate -a -R $t $pruned; then - (( repeattime = REPLY, repeating = 1 )) - fi + if [[ -n ${reply[rpttime]} ]]; then + (( repeattime = ${reply[rpttime]}, repeating = 1 )) + else + (( repeating = 0 )) fi + # Finished extracting parameters from $reply if (( verbose )); then print "Examining: $line" diff --git a/Functions/Calendar/calendar_add b/Functions/Calendar/calendar_add index dc9f50c2b..6c61a3cba 100644 --- a/Functions/Calendar/calendar_add +++ b/Functions/Calendar/calendar_add @@ -11,10 +11,11 @@ emulate -L zsh setopt extendedglob local calendar newfile REPLY lastline opt -local -a calendar_entries lockfiles -integer my_date done rstat nolock nobackup +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_{read,lockfiles,scandate} +autoload -U calendar_{parse,read,lockfiles} while getopts "BL" opt; do case $opt in @@ -38,11 +39,13 @@ zstyle -s ':datetime:calendar_add:' calendar-file calendar || calendar=~/calendar newfile=$calendar.new.$HOST.$$ -if ! calendar_scandate -a "$*"; then +if ! calendar_parse "$*"; then print "$0: failed to parse date/time" >&2 return 1 fi -(( my_date = $REPLY )) +parse_new=("${(@kv)reply}") +(( my_date = $parse_new[time] )) +[[ -n $parse_new[rpttime] ]] && (( new_recurring = 1 )) # $calendar doesn't necessarily exist yet. @@ -53,7 +56,7 @@ local my_uid their_uid # text/calendar format. local uidpat='(|*[[:space:]])UID[[:space:]]##(#b)([[:xdigit:]]##)(|[[:space:]]*)' if [[ "$*" = ${~uidpat} ]]; then - my_uid=$match[1] + my_uid=${(U)match[1]} fi # start of block for following always to clear up lockfiles. @@ -63,16 +66,55 @@ fi 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]=1 + fi + done + fi + { for line in $calendar_entries; do - if (( ! done )) && calendar_scandate -a $line && (( REPLY > my_date )); then + calendar_parse $line || continue + parse_old=("${(@kv)reply}") + if (( ! done && ${parse_old[time]} > my_date )); then print -r -- "$*" (( done = 1 )) fi - # Don't save this entry if it has the same UID as the new one. + if [[ -n $parse_old[rpttime] ]]; then + (( old_recurring = 1 )) + else + (( old_recurring = 0 )) + fi if [[ -n $my_uid && $line = ${~uidpat} ]]; then - their_uid=$match[1] - [[ ${(U)my_uid} = ${(U)their_uid} ]] && continue + 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 + fi + fi fi if [[ $REPLY -eq $my_date && $line = "$*" ]]; then (( done )) && continue # paranoia: shouldn't happen diff --git a/Functions/Calendar/calendar_parse b/Functions/Calendar/calendar_parse new file mode 100644 index 000000000..f856a4f77 --- /dev/null +++ b/Functions/Calendar/calendar_parse @@ -0,0 +1,83 @@ +# Parse the line passed down in the first argument as a calendar entry. +# Sets the values parsed into the associative array reply, consisting of: +# time The time as an integer (as per EPOCHSECONDS) +# text1 The text from the the line not including the date/time, but +# including any WARN or RPT text. This is useful for rescheduling +# events, since the keywords need to be retained in this case. +# warntime Any warning time (WARN keyword) as an integer, else an empty +# string. This is the time of the warning in units of EPOCHSECONDS, +# not the parsed version of the original number (which was a time +# difference). +# warnstr Any warning time as the original string (e.g. "5 mins"), not +# including the WARN keyword. +# rpttime Any repeat/recurrence time (RPT keyword) as an integer, else empty. +# This is the time of the recurrence itself in EPOCHSECONDS units +# (as with a warning---not the difference between the events). +# rptstr Any repeat/recurrence time as the original string. +# text2 The text from the line with the date and keywords and values removed. +# +# Note that here an "integer" is a string of digits, not an internally +# formatted integer. +# +# Return status 1 if parsing failed. reply is set to an empty +# in this case. Note the caller is responsible for +# making reply local. + +emulate -L zsh +setopt extendedglob + +local REPLY REPLY2 +local -a match mbegin mend + +autoload -U calendar_scandate + +typeset -gA reply + +reply=() + +if (( $# != 1 )); then + print "Usage: $0 calendar-entry" >&2 + return 2 +fi + +# This call sets REPLY to the date and time in seconds since the epoch, +# REPLY2 to the line with the date and time removed. +calendar_scandate -as $1 || return 1 +reply[time]=$(( REPLY )) +reply[text1]=${REPLY2##[[:space:]]#} + +reply[text2]=$reply[text1] + +integer changed=1 + +while (( changed )); do + + (( changed = 0 )) + + # Look for specific warn time. + if [[ $reply[text2] = (#b)(|*[[:space:],])WARN[[:space:]](*) ]]; then + if calendar_scandate -asm -R $reply[time] $match[2]; then + reply[warntime]=$REPLY + reply[warnstr]=${match[2]%%"$REPLY2"} + reply[text2]="${match[1]}${REPLY2##[[:space:]]#}" + else + # Just remove the keyword for further parsing + reply[text2]="${match[1]}${match[2]##[[:space:]]#}" + fi + (( changed = 1 )) + elif [[ $reply[text2] = (#b)(|*[[:space:],])RPT[[:space:]](*) ]]; then + if calendar_scandate -a -R $reply[time] $match[2]; then + reply[rpttime]=$REPLY + reply[rptstr]=${match[2]%%"$REPLY2"} + reply[text2]="${match[1]}${REPLY2##[[:space:]]#}" + else + # Just remove the keyword for further parsing + reply[text2]="${match[1]}${match[2]##[[:space:]]#}" + fi + (( changed = 1 )) + fi +done + +reply[text2]="${reply[text2]##[[:space:],]#}" + +return 0 |