1
0
Fork 0
mirror of git://git.code.sf.net/p/zsh/code synced 2025-01-23 00:41:03 +01:00
zsh/Functions/Calendar/calendar
2006-12-01 10:23:06 +00:00

356 lines
8.8 KiB
Text

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