From 6b1b34d1da6b0db599c026e17df011ad6c6b3a30 Mon Sep 17 00:00:00 2001 From: Peter Stephenson Date: Fri, 1 Dec 2006 10:23:06 +0000 Subject: c.f. 23023: new calendar function system. --- Functions/Calendar/calendar | 356 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 356 insertions(+) create mode 100644 Functions/Calendar/calendar (limited to 'Functions/Calendar/calendar') diff --git a/Functions/Calendar/calendar b/Functions/Calendar/calendar new file mode 100644 index 000000000..124fd9786 --- /dev/null +++ b/Functions/Calendar/calendar @@ -0,0 +1,356 @@ +emulate -L zsh +setopt extendedglob + +# standard ctime date/time format +local ctime="%a %b %d %H:%M:%S %Z %Y" + +local line REPLY REPLY2 userange pruned +local calendar donefile sched newfile warnstr mywarnstr +integer time start stop today ndays y m d next=-1 shown done nodone +integer verbose warntime mywarntime t tsched i rstat remaining +integer showcount icount +local -a calendar_entries +local -a times calopts showprog lockfiles match mbegin mend + +zmodload -i zsh/datetime || return 1 +zmodload -i zsh/zutil || return 1 + +autoload -U calendar_{read,scandate,show,lockfiles} + +# Read the calendar file from the calendar-file style +zstyle -s ':datetime:calendar:' calendar-file calendar || calendar=~/calendar +newfile=$calendar.new.$HOST.$$ +zstyle -s ':datetime:calendar:' done-file donefile || donefile="$calendar.done" +# Read the programme to show the message from the show-prog style. +zstyle -a ':datetime:calendar:' show-prog showprog || + showprog=(calendar_show) +# Amount of time before an event when it should be flagged. +# May be overridden in individual entries +zstyle -s ':datetime:calendar:' warn-time warnstr || warnstr="0:05" + +if [[ -n $warnstr ]]; then + if [[ $warnstr = <-> ]]; then + (( warntime = warnstr )) + elif ! calendar_scandate -ar $warnstr; then + print >&2 \ + "warn-time value '$warnstr' not understood; using default 5 minutes" + warnstr="5 mins" + (( warntime = 5 * 60 )) + else + (( warntime = REPLY )) + fi +fi + +[[ -f $calendar ]] || return 1 + +# We're not using getopts because we want +... to refer to a +# relative time, not an option, and allow some other additions +# like handling -<->. +integer opti=0 +local opt optrest optarg + +while [[ ${argv[opti+1]} = -* ]]; do + (( opti++ )) + opt=${argv[opti][2]} + optrest=${argv[opti][3,-1]} + [[ -z $opt || $opt = - ]] && break + while [[ -n $opt ]]; do + case $opt in + ######################## + # Options with arguments + ######################## + ([CnS]) + if [[ -n $optrest ]]; then + optarg=$optrest + optrest= + elif (( opti < $# )); then + optarg=$argv[++opti] + optrest= + else + print -r "$0: option -$opt requires an argument." >&2 + return 1 + fi + case $opt in + (C) + # Pick the calendar file, overriding style and default. + calendar=$optarg + ;; + + (n) + # Show this many remaining events regardless of date. + showcount=$optarg + if (( showcount <= 0 )); then + print -r "$0: option -$opt requires a positive integer." >&2 + return 1 + fi + ;; + + (S) + # Explicitly specify a show programme, overriding style and default. + # Colons in the argument are turned into space. + showprog=(${(s.:.)optarg}) + ;; + esac + ;; + + ########################### + # Options without arguments + ########################### + (d) + # Move out of date items to the done file. + (( done = 1 )) + ;; + + (D) + # Don't use done; needed with sched + (( nodone = 1 )) + ;; + + (r) + # Show all remaining options in the calendar, i.e. + # respect start time but ignore end time. + # Any argument is treated as a start time. + (( remaining = 1 )) + ;; + + (s) + # Use the "sched" builtin to scan at the appropriate time. + sched=sched + (( done = 1 )) + ;; + + (v) + # Verbose + verbose=1 + ;; + + (<->) + # Shorthand for -n <-> + showcount=$opt + ;; + + (*) + print "$0: unrecognised option: -$opt" >&2 + return 1 + ;; + esac + opt=$optrest[1] + optrest=$optrest[2,-1] + done +done +calopts=($argv[1,opti]) +shift $(( opti )) + +# Use of donefile requires explicit or implicit option request, plus +# no explicit -D. It may already be empty because of the style. +(( done && !nodone )) || donefile= + +if (( $# > 1 || ($# == 1 && remaining) )); then + if [[ $1 = now ]]; then + start=$EPOCHSECONDS + elif [[ $1 = <-> ]]; then + start=$1 + else + if ! calendar_scandate -a $1; then + print "$0: failed to parse date/time: $1" >&2 + return 1 + fi + start=$REPLY + fi + shift +else + # Get the time at which today started. + y=${(%):-"%D{%Y}"} m=${(%):-"%D{%m}"} d=${(%):-"%D{%d}"} + strftime -s today -r "%Y/%m/%d" "$y/$m/$d" + start=$today +fi +# day of week of start time +strftime -s wd "%u" $start + +if (( $# && !remaining )); then + if [[ $1 = +* ]]; then + if ! calendar_scandate -ar ${1[2,-1]}; then + print "$0: failed to parse relative time: $1" >&2 + return 1 + fi + (( stop = start + REPLY )) + elif [[ $1 = <-> ]]; then + stop=$1 + else + if ! calendar_scandate -a $1; then + print "$0: failed to parse date/time: $1" >&2 + return 1 + fi + stop=$REPLY + fi + if (( stop < start )); then + strftime -s REPLY $ctime $start + strftime -s REPLY2 $ctime $stop + print "$0: requested end time is before start time: + start: $REPLY + end: $REPLY2" >&2 + return 1 + fi + shift +else + # By default, show 2 days. If it's Friday (5) show up to end + # of Monday (4) days; likewise on Saturday show 3 days. + # If -r, this is calculated but not used. This is paranoia, + # to avoid an unusable value of stop; but it shouldn't get used. + case $wd in + (5) + ndays=4 + ;; + + (6) + ndays=3 + ;; + + (*) + ndays=2 + ;; + esac + stop=$(( start + ndays * 24 * 60 * 60 )) +fi + +if (( $# )); then + print "Usage: $0 [ start-date-time stop-date-time ]" >&2 + return 1 +fi + +autoload -Uz matchdate + +[[ -n $donefile ]] && rm -f $newfile + +if (( verbose )); then + print -n "start: " + strftime $ctime $start + print -n "stop: " + if (( remaining )); then + print "none" + else + strftime $ctime $stop + fi +fi + +# start of block for following always to clear up lockfiles. +{ + if [[ -n $donefile ]]; then + # Attempt to lock both $donefile and $calendar. + # Don't lock $newfile; we've tried our best to make + # the name unique. + calendar_lockfiles $calendar $donefile || return 1 + 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 )) + + # Look for specific warn time. + pruned=${REPLY2#(|*[[:space:],])WARN[[:space:]]} + (( mywarntime = warntime )) + mywarnstr=$warnstr + if [[ $pruned != $REPLY2 ]]; then + if calendar_scandate -ars $pruned; then + (( mywarntime = REPLY )) + mywarnstr=${pruned%%"$REPLY2"} + fi + fi + + if (( verbose )); then + print "Examining: $line" + print -n " Date/time: " + strftime $ctime $t + if [[ -n $sched ]]; then + print " Warning $mywarntime seconds ($mywarnstr) before" + fi + fi + (( shown = 0 )) + if (( t >= start && (remaining || t <= stop || icount < showcount) )) + then + $showprog $start $stop "$line" + (( shown = 1, icount++ )) + elif [[ -n $sched ]]; then + (( tsched = t - mywarntime )) + if (( tsched >= start && tsched <= stop)); then + $showprog $start $stop "due in ${mywarnstr}: $line" + fi + fi + if [[ -n $sched ]]; then + if (( t - mywarntime > EPOCHSECONDS )); then + # schedule for a warning + (( tsched = t - mywarntime )) + else + # schedule for event itself + (( tsched = t )) + fi + if (( (tsched > EPOCHSECONDS || ! shown) && + (next < 0 || tsched < next) )); then + (( next = tsched )) + fi + fi + if [[ -n $donefile ]]; then + if (( t <= EPOCHSECONDS && shown )); then + # Done and dusted. + # TODO: handle repeated times from REPLY2. + if ! print -r $line >>$donefile; then + if (( done != 3 )); then + (( done = 3 )) + print "Failed to append to $donefile" >&2 + fi + elif (( done != 3 )); then + (( done = 2 )) + fi + else + # Still not over. + if ! print -r $line >>$newfile; then + if (( done != 3 )); then + (( done = 3 )) + print "Failed to append to $newfile" >&2 + fi + elif (( done != 3 )); then + (( done = 2 )) + fi + fi + fi + done + + if [[ -n $sched ]]; then + if [[ $next -ge 0 ]]; then + # Remove any existing calendar scheduling. + # Luckily sched doesn't delete its schedule in a subshell. + sched | while read line; do + if [[ $line = (#b)[[:space:]]#(<->)[[:space:]]##*[[:space:]]'calendar -s'* ]]; then + # End of pipeline run in current shell, so delete directly. + sched -1 $match[1] + fi + done + $sched $next calendar "${calopts[@]}" $next $next + else + $showprog $start $stop \ +"No more calendar events: calendar not rescheduled. +Run \"calendar -s\" again if you add to it." + fi + fi + + if (( done == 2 )); then + if ! mv $calendar $calendar.old; then + print "Couldn't back up $calendar to $calendar.old. +New calendar left in $newfile." >&2 + (( rstat = 1 )) + elif ! mv $newfile $calendar; then + print "Failed to rename $newfile to $calendar. +Old calendar left in $calendar.old." >&2 + (( rstat = 1 )) + fi + elif [[ -n $donefile ]]; then + rm -f $newfile + fi +} always { + (( ${#lockfiles} )) && rm -f $lockfiles +} + +return $rstat -- cgit 1.4.1