Zsh Mailing List Archive
Messages sorted by:
Reverse Date,
Date,
Thread,
Author
PATCH: improve calendar handling of recurring events
- X-seq: zsh-workers 24130
- From: Peter Stephenson <pws@xxxxxxx>
- To: zsh-workers@xxxxxxxxxx (Zsh hackers list)
- Subject: PATCH: improve calendar handling of recurring events
- Date: Thu, 29 Nov 2007 09:57:57 +0000
- Mailing-list: contact zsh-workers-help@xxxxxxxxxx; run by ezmlm
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 <pws@xxxxxxx> Software Engineer
CSR PLC, Churchill House, Cambridge Business Park, Cowley Road
Cambridge, CB4 0WZ, UK Tel: +44 (0)1223 692070
Messages sorted by:
Reverse Date,
Date,
Thread,
Author