mirror of
git://git.code.sf.net/p/zsh/code
synced 2025-01-01 17:24:50 +01:00
445 lines
12 KiB
Text
445 lines
12 KiB
Text
emulate -L zsh
|
|
setopt extendedglob
|
|
|
|
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 tmplines
|
|
local -A reply
|
|
|
|
zmodload -i zsh/datetime || return 1
|
|
zmodload -i zsh/zutil || return 1
|
|
zmodload -F zsh/files b:zf_ln || return 1
|
|
|
|
autoload -Uz 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
|
|
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"
|
|
# default to standard ctime date/time format
|
|
zstyle -s ':datetime:calendar:' date-format datefmt ||
|
|
datefmt="%a %b %d %H:%M:%S %Z %Y"
|
|
|
|
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
|
|
########################
|
|
([BCnS])
|
|
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
|
|
(B)
|
|
# Brief, with number of lines to show.
|
|
brief=$optarg
|
|
if (( brief <= 0 )); then
|
|
print -r "$0: option -$opt requires a positive integer." >&2
|
|
return 1
|
|
fi
|
|
;;
|
|
|
|
(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
|
|
###########################
|
|
(a)
|
|
# Show all entries
|
|
(( showall = 1 ))
|
|
;;
|
|
|
|
(b)
|
|
# Brief: don't show continuation lines
|
|
(( brief = 1 ))
|
|
;;
|
|
|
|
(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 -a -R $start ${1[2,-1]}; then
|
|
print "$0: failed to parse relative time: $1" >&2
|
|
return 1
|
|
fi
|
|
(( stop = 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 $datefmt $start
|
|
strftime -s REPLY2 $datefmt $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 $datefmt $start
|
|
print -n "stop: "
|
|
if (( remaining )); then
|
|
print "none"
|
|
else
|
|
strftime $datefmt $stop
|
|
fi
|
|
fi
|
|
|
|
local mycmds="${TMPPREFIX:-/tmp/zsh}.calendar_cmds.$$"
|
|
zf_ln -fn =(<<<'') $mycmds || return 1
|
|
|
|
# start of subshell for OS file locking
|
|
(
|
|
# start of block for following always to clear up lockfiles.
|
|
# Not needed but harmless if OS file locking is used.
|
|
{
|
|
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.
|
|
if zmodload -F zsh/system b:zsystem && zsystem supports flock &&
|
|
zsystem flock $calendar 2>/dev/null &&
|
|
zsystem flock $donefile 2>/dev/null; then
|
|
# locked OK
|
|
:
|
|
else
|
|
calendar_lockfiles $calendar $donefile || exit 1
|
|
fi
|
|
fi
|
|
|
|
calendar_read $calendar
|
|
for line in $calendar_entries; do
|
|
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.
|
|
if [[ -n ${reply[warntime]} ]]; then
|
|
(( mywarntime = t - ${reply[warntime]} ))
|
|
mywarnstr=${reply[warnstr]}
|
|
else
|
|
(( mywarntime = warntime ))
|
|
mywarnstr=$warnstr
|
|
fi
|
|
# Look for a repeat time.
|
|
if [[ -n ${reply[rpttime]} ]]; then
|
|
# The actual time of the next event, which appears as text
|
|
(( repeattime = ${reply[rpttime]} ))
|
|
(( repeating = 1 ))
|
|
else
|
|
(( repeating = 0 ))
|
|
fi
|
|
# Finished extracting parameters from $reply
|
|
|
|
if (( verbose )); then
|
|
print "Examining: $line"
|
|
print -n " Date/time: "
|
|
strftime $datefmt $t
|
|
if [[ -n $sched ]]; then
|
|
print " Warning $mywarntime seconds ($mywarnstr) before"
|
|
fi
|
|
fi
|
|
(( shown = 0 ))
|
|
if (( brief )); then
|
|
tmplines=("${(f)line}")
|
|
showline=${(F)${${tmplines[1,brief]}}}
|
|
else
|
|
showline=$line
|
|
fi
|
|
match=()
|
|
# Strip continuation lines starting " #".
|
|
while [[ $showline = (#b)(*$'\n')[[:space:]]##\#[^$'\n']##(|$'\n'(*)) ]]; do
|
|
showline="$match[1]$match[3]"
|
|
done
|
|
# Strip trailing empty lines
|
|
showline=${showline%%[[:space:]]#}
|
|
if (( showall || (t >= start && (remaining || t <= stop || icount < showcount)) ))
|
|
then
|
|
print -r -- ${(qq)showprog} $start $stop ${(qq)showline} >>$mycmds
|
|
(( icount++ ))
|
|
# Doesn't count as "shown" unless the event has now passed.
|
|
(( t <= EPOCHSECONDS )) && (( shown = 1 ))
|
|
elif [[ -n $sched ]]; then
|
|
(( tsched = t - mywarntime ))
|
|
if (( tsched >= start && tsched <= stop)); then
|
|
showline="due in ${mywarnstr}: $showline"
|
|
print -r -- ${(qq)showprog} $start $stop ${(qq)showline} >>$mycmds
|
|
elif (( tsched < start )); then
|
|
# We haven't actually shown it, but it's in the past,
|
|
# so treat it the same. Should probably rename this variable.
|
|
(( shown = 1 ))
|
|
fi
|
|
fi
|
|
if [[ -n $sched ]]; then
|
|
if (( shown && repeating )); then
|
|
# Done and dusted, but a repeated event is due.
|
|
strftime -s newdate $datefmt $repeattime
|
|
if [[ $newdate != *[[:space:]] && $restline != [[:space:]]* ]]; then
|
|
newdate+=" "
|
|
fi
|
|
calendar_addlines+=("$newdate$restline")
|
|
|
|
# We'll add this back in below, but we check in case the
|
|
# repeated event is the next one due. It's not
|
|
# actually a disaster if there's an error and we fail
|
|
# to add the time. Always try to reschedule this event.
|
|
(( tcalc = repeattime, resched = 1 ))
|
|
else
|
|
(( tcalc = t ))
|
|
fi
|
|
|
|
if (( tcalc - mywarntime > EPOCHSECONDS )); then
|
|
# schedule for a warning
|
|
(( tsched = tcalc - mywarntime, resched = 1 ))
|
|
else
|
|
# schedule for event itself
|
|
(( tsched = tcalc ))
|
|
# but don't schedule unless the event has not yet been shown.
|
|
(( !shown )) && (( resched = 1 ))
|
|
fi
|
|
if (( resched && (next < 0 || tsched < next) )); then
|
|
(( next = tsched ))
|
|
fi
|
|
fi
|
|
if [[ -n $donefile ]]; then
|
|
if (( shown )); then
|
|
# Done and dusted.
|
|
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.
|
|
i=${"${(@)zsh_scheduled_events#*:*:}"[(I)calendar -s*]}
|
|
{
|
|
(( i )) && print sched -$i
|
|
print -r -- ${(qq)sched} $next calendar "${calopts[@]}" $next $next
|
|
} >>$mycmds
|
|
else
|
|
showline="No more calendar events: calendar not rescheduled.
|
|
Run \"calendar -s\" again if you add to it."
|
|
print -r -- ${(qq)showprog} $start $stop ${(qq)showline} >>$mycmds
|
|
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
|
|
nobackup=-B
|
|
elif [[ -n $donefile ]]; then
|
|
rm -f $newfile
|
|
fi
|
|
|
|
# Reschedule repeating events.
|
|
for line in $calendar_addlines; do
|
|
calendar_add -L $nobackup $line
|
|
done
|
|
} always {
|
|
(( ${#lockfiles} )) && rm -f $lockfiles
|
|
}
|
|
|
|
exit $rstat
|
|
) && {
|
|
# Tasks that need to be in the current shell
|
|
[[ -s $mycmds ]] && . $mycmds
|
|
rm -f $mycmds
|
|
}
|