summaryrefslogtreecommitdiff
path: root/Functions/Calendar/calendar
diff options
context:
space:
mode:
Diffstat (limited to 'Functions/Calendar/calendar')
-rw-r--r--Functions/Calendar/calendar356
1 files changed, 356 insertions, 0 deletions
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