about summary refs log tree commit diff
path: root/Functions
diff options
context:
space:
mode:
Diffstat (limited to 'Functions')
-rw-r--r--Functions/Calendar/.distfiles23
-rw-r--r--Functions/Calendar/calendar45
-rw-r--r--Functions/Calendar/calendar_add62
-rw-r--r--Functions/Calendar/calendar_parse83
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