1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176
|
# 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) of the (next) event.
# text1 The text from 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.
# schedrpttime The next scheduled recurrence (which may be cancelled
# or rescheduled).
# rpttime The actual occurrence time: the event may have been rescheduled,
# in which case this is the time of the actual event (for use in
# programming warnings etc.) rather than that of the normal
# recurrence (which is recorded by calendar_add as RECURRENCE).
#
# rptstr Any repeat/recurrence time as the original string.
# text2 The text from the line with the date and other 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 vdatefmt="%Y%m%dT%H%M%S"
local REPLY REPLY2 timefmt occurrence skip try_to_recover before after
local -a match mbegin mend
integer now then replaced firstsched schedrpt
# Any text matching "OCCURRENCE <timestamp> <disposition>"
# may occur multiple times. We set occurrences[<timestamp>]=disposition.
local -A occurrences
autoload -Uz 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 ))
schedrpt=${reply[time]}
reply[text1]=${REPLY2##[[:space:]]#}
reply[text2]=${reply[text1]}
while true; do
case ${reply[text2]} in
# First check for a scheduled repeat time. If we don't find one
# we'll use the normal time.
((#b)(*[[:space:]\#])RECURRENCE[[:space:]]##([^[:space:]]##)([[:space:]]*|))
strftime -rs then $vdatefmt ${match[2]} ||
print "format: $vdatefmt, string ${match[2]}" >&2
schedrpt=$then
reply[text2]="${match[1]}${match[3]##[ ]#}"
;;
# Look for specific warn time.
((#b)(|*[[:space:],])WARN[[:space:]](*))
if calendar_scandate -asm -R $reply[time] $match[2]; then
reply[warntime]=$REPLY
reply[warnstr]=${match[2]%%"$REPLY2"}
# Remove spaces and tabs but not newlines from trailing text,
# else the formatting looks funny.
reply[text2]="${match[1]}${REPLY2##[ ]#}"
else
# Just remove the keyword for further parsing
reply[text2]="${match[1]}${match[2]##[ ]#}"
fi
;;
((#b)(|*[[:space:],])RPT[[:space:]](*))
before=${match[1]}
after=${match[2]}
if [[ $after = CANCELLED(|[[:space:]]*) ]]; then
reply[text2]="$before${match[2]##[ ]#}"
reply[rptstr]=CANCELLED
reply[rpttime]=CANCELLED
reply[schedrpttime]=CANCELLED
elif calendar_scandate -a -R $schedrpt $after; then
# It's possible to calculate a recurrence, however we don't
# do that yet. For now just keep the current time as
# the recurrence. Hence we ignore REPLY.
reply[text2]="$before${REPLY2##[ ]#}"
reply[rptstr]=${after%%"$REPLY2"}
# Until we find an individual occurrence, the actual time
# of the event is the regular one.
reply[rpttime]=$schedrpt
else
# Just remove the keyword for further parsing
reply[text2]="$before${after##[[:space:]]#}"
fi
;;
((#b)(|*[[:space:]\#])OCCURRENCE[[:space:]]##([^[:space:]]##)[[:space:]]##([^[:space:]]##)(*))
occurrences[${match[2]}]="${match[3]}"
# as above
reply[text2]="${match[1]}${match[4]##[ ]#}"
;;
(*)
break
;;
esac
done
if [[ -n ${reply[rpttime]} && ${reply[rptstr]} != CANCELLED ]]; then
# Recurring event. We need to find out when it recurs.
(( now = EPOCHSECONDS ))
# First find the next recurrence.
replaced=0
reply[schedrpttime]=$schedrpt
if (( schedrpt >= now )); then
firstsched=$schedrpt
fi
while (( ${reply[schedrpttime]} < now || replaced )); do
if ! calendar_scandate -a -R ${reply[schedrpttime]} ${reply[rptstr]}; then
break
fi
if (( REPLY <= ${reply[schedrpttime]} )); then
# going backwards --- pathological case
break;
fi
reply[schedrpttime]=$REPLY
reply[rpttime]=$REPLY
if (( ${reply[schedrpttime]} > now && firstsched == 0 )); then
firstsched=$REPLY
fi
replaced=0
# do we have an occurrence to compare against?
if (( ${#occurrences} )); then
strftime -s timefmt $vdatefmt ${reply[schedrpttime]}
occurrence=$occurrences[$timefmt]
if [[ -n $occurrence ]]; then
# Yes, this replaces the scheduled one.
replaced=1
fi
fi
done
# Now look through occurrences (values only) and see which are (i) still
# to happen (ii) early than the current rpttime.
for occurrence in $occurrences; do
if [[ $occurrence != CANCELLED ]]; then
strftime -rs then $vdatefmt $occurrence ||
print "format: $vdatefmt, string $occurrence" >&2
if (( then > now && then < ${reply[rpttime]} )); then
reply[rpttime]=$then
fi
fi
done
# Finally, update the scheduled repeat time to the earliest
# possible value. This is so that if an occurrence replacement is
# cancelled we pick up the regular one. Can this happen? Dunno.
reply[schedrpttime]=$firstsched
fi
reply[text2]="${reply[text2]##[[:space:],]#}"
return 0
|