From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: (qmail 20704 invoked from network); 29 Nov 2007 09:58:23 -0000 X-Spam-Checker-Version: SpamAssassin 3.2.3 (2007-08-08) on f.primenet.com.au X-Spam-Level: X-Spam-Status: No, score=-2.5 required=5.0 tests=AWL,BAYES_00 autolearn=ham version=3.2.3 Received: from news.dotsrc.org (HELO a.mx.sunsite.dk) (130.225.247.88) by ns1.primenet.com.au with SMTP; 29 Nov 2007 09:58:23 -0000 Received-SPF: none (ns1.primenet.com.au: domain at sunsite.dk does not designate permitted sender hosts) Received: (qmail 30290 invoked from network); 29 Nov 2007 09:58:18 -0000 Received: from sunsite.dk (130.225.247.90) by a.mx.sunsite.dk with SMTP; 29 Nov 2007 09:58:18 -0000 Received: (qmail 10382 invoked by alias); 29 Nov 2007 09:58:15 -0000 Mailing-List: contact zsh-workers-help@sunsite.dk; run by ezmlm Precedence: bulk X-No-Archive: yes X-Seq: 24130 Received: (qmail 10364 invoked from network); 29 Nov 2007 09:58:15 -0000 Received: from news.dotsrc.org (HELO a.mx.sunsite.dk) (130.225.247.88) by sunsite.dk with SMTP; 29 Nov 2007 09:58:15 -0000 Received: (qmail 30033 invoked from network); 29 Nov 2007 09:58:15 -0000 Received: from cluster-g.mailcontrol.com (85.115.41.190) by a.mx.sunsite.dk with SMTP; 29 Nov 2007 09:58:07 -0000 Received: from cameurexb01.EUROPE.ROOT.PRI ([62.189.241.200]) by rly15g.srv.mailcontrol.com (MailControl) with ESMTP id lAT9vs5w027165 for ; Thu, 29 Nov 2007 09:58:01 GMT Received: from news01.csr.com ([10.103.143.38]) by cameurexb01.EUROPE.ROOT.PRI with Microsoft SMTPSVC(6.0.3790.1830); Thu, 29 Nov 2007 09:57:57 +0000 Received: from news01.csr.com (localhost.localdomain [127.0.0.1]) by news01.csr.com (8.14.1/8.13.4) with ESMTP id lAT9vvQa016042 for ; Thu, 29 Nov 2007 09:57:57 GMT Received: from csr.com (pws@localhost) by news01.csr.com (8.14.1/8.14.1/Submit) with ESMTP id lAT9vvLO016039 for ; Thu, 29 Nov 2007 09:57:57 GMT X-Authentication-Warning: news01.csr.com: pws owned process doing -bs To: zsh-workers@sunsite.dk (Zsh hackers list) Subject: PATCH: improve calendar handling of recurring events X-Mailer: MH-E 8.0.3; nmh 1.2-20070115cvs; GNU Emacs 22.1.1 Date: Thu, 29 Nov 2007 09:57:57 +0000 Message-ID: <16038.1196330277@csr.com> From: Peter Stephenson X-OriginalArrivalTime: 29 Nov 2007 09:57:58.0005 (UTC) FILETIME=[51CA1A50:01C8326E] X-Scanned-By: MailControl A-06-00-00 (www.mailcontrol.com) on 10.71.0.125 I had this carefully uncommitted, but inevitably it decided to sneak in with another commit, so I might as well leave it... The abstracts the function calendar_parse to parse a calendar entry from the function calendar, and uses it in calendar_add to check whether an event is recurring. If it is, then one-off events supplement rather than replace it. This is still crude but will at least stop all my regular meetings infuriatingly disappearing every time one occurrence moves. I was hoping to test it a bit more first. By the way, as I update the various .distfiles I'm (very) gradually moving them to one entry per line in alphabetical order. This is much easier to maintain and actually slightly more compact in storage; there's absolutely no point in having it prettified. The change is pretty much invisible to everyone else anyway. Index: Doc/Zsh/calsys.yo =================================================================== RCS file: /cvsroot/zsh/zsh/Doc/Zsh/calsys.yo,v retrieving revision 1.10 retrieving revision 1.11 diff -u -r1.10 -r1.11 --- Doc/Zsh/calsys.yo 16 Aug 2007 12:04:06 -0000 1.10 +++ Doc/Zsh/calsys.yo 29 Nov 2007 09:49:43 -0000 1.11 @@ -464,6 +464,36 @@ Hence it should be used to edit the calendar file if there is any possibility of a calendar event occurring meanwhile. ) +findex(calendar_parse) +item(tt(calendar_parse) var(calendar-entry))( +This is the internal function that analyses the parts of a calendar +entry, which is passed as the only argument. The function returns +status 1 if the argument could not be parsed as a calendar entry +and status 2 if the wrong number of arguments were passed; it also sets the +parameter tt(reply) to an empty associative array. Otherwise, +it returns status 0 and sets elements of the associative +array tt(reply) as follows: +startsitem() +sitem(time)(The time as a string of digits in the same units as +tt($EPOCHSECONDS)) +sitem(text1)(The text from the line not including the date and time of the +event, but including any tt(WARN) or tt(RPT) keywords and values.) +sitem(warntime)(Any warning time given by the tt(WARN) keyword as a string +of digits containing the time at which to warn in the same units as +tt($EPOCHSECONDS). (Note this is an absolute time, not the relative time +passed down.) Not set no tt(WARN) keyword and value were +matched.) +sitem(warnstr)(The raw string matched after the tt(WARN) keyword, else unset.) +sitem(rpttime)(Any recurrence time given by the tt(RPT) keyword as a string +of digits containing the time of the recurrenced in the same units +as tt($EPOCHSECONDS). (Note this is an absolute time.) Not set if +no tt(RPT) keyword and value were matched.) +sitem(rptstr)(The raw string matched after the tt(RPT) keyword, else unset.) +sitem(text2)(The text from the line after removal of the date and any +keywords and values.) +) +endsitem() +) findex(calendar_showdate) item(tt(calendar_showdate) [ tt(-r) ] [ tt(-f) var(fmt) ] var(date-spec ...))( The given var(date-spec) is interpreted and the corresponding date and Index: Functions/Calendar/.distfiles =================================================================== RCS file: /cvsroot/zsh/zsh/Functions/Calendar/.distfiles,v retrieving revision 1.2 retrieving revision 1.3 diff -u -r1.2 -r1.3 --- Functions/Calendar/.distfiles 16 Apr 2007 13:21:26 -0000 1.2 +++ Functions/Calendar/.distfiles 29 Nov 2007 09:49:43 -0000 1.3 @@ -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 ' Index: Functions/Calendar/calendar =================================================================== RCS file: /cvsroot/zsh/zsh/Functions/Calendar/calendar,v retrieving revision 1.7 retrieving revision 1.8 diff -u -r1.7 -r1.8 --- Functions/Calendar/calendar 5 Sep 2007 08:34:27 -0000 1.7 +++ Functions/Calendar/calendar 29 Nov 2007 09:49:43 -0000 1.8 @@ -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 @@ 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" Index: Functions/Calendar/calendar_add =================================================================== RCS file: /cvsroot/zsh/zsh/Functions/Calendar/calendar_add,v retrieving revision 1.4 retrieving revision 1.5 diff -u -r1.4 -r1.5 --- Functions/Calendar/calendar_add 16 Aug 2007 12:04:08 -0000 1.4 +++ Functions/Calendar/calendar_add 29 Nov 2007 09:49:43 -0000 1.5 @@ -11,10 +11,11 @@ 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 @@ 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 @@ # 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 @@ 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 Index: Functions/Calendar/calendar_parse =================================================================== RCS file: Functions/Calendar/calendar_parse diff -N Functions/Calendar/calendar_parse --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ Functions/Calendar/calendar_parse 29 Nov 2007 09:49:43 -0000 1.1 @@ -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 -- Peter Stephenson Software Engineer CSR PLC, Churchill House, Cambridge Business Park, Cowley Road Cambridge, CB4 0WZ, UK Tel: +44 (0)1223 692070