1
0
Fork 0
mirror of git://git.code.sf.net/p/zsh/code synced 2024-12-29 16:25:35 +01:00

c.f. 23023: new calendar function system.

This commit is contained in:
Peter Stephenson 2006-12-01 10:23:06 +00:00
parent ab8b8026dc
commit 6b1b34d1da
23 changed files with 1787 additions and 8 deletions

View file

@ -1,3 +1,19 @@
2006-12-01 Peter Stephenson <pws@csr.com>
* c.f. 23023: Completion/Unix/Type/_list_files, Doc/.distfiles,
Doc/Makefile.in, Doc/zsh.yo, Doc/zshcalsys.yo, Doc/Zsh/.distfiles,
Doc/Zsh/calsys.yo, Doc/Zsh/compsys.yo, Doc/Zsh/intro.yo,
Doc/Zsh/manual.yo, Doc/Zsh/modules.yo, Doc/Zsh/tcpsys.yo,
Functions/Calendar/.distfiles, Functions/Calendar/age,
Functions/Calendar/calendar, Functions/Calendar/calendar_add,
Functions/Calendar/calendar_lockfiles,
Functions/Calendar/calendar_read,
Functions/Calendar/calendar_scandate,
Functions/Calendar/calendar_show,
Functions/Calendar/calendar_sort, Src/Modules/datetime.mdd: new
calendar system with age glob qualifier function; files
_list_files to be able not to trample over external stat.
2006-11-28 Peter Stephenson <p.w.stephenson@ntlworld.com>
* 23022: Test/ztst.zsh: don't allow WORDCHARS to be exported

View file

@ -48,6 +48,13 @@ done
zmodload -i zsh/stat 2>/dev/null || return 1
# Enable stat temporarily if disabled to avoid clashes.
integer disable_stat
if [[ ${builtins[stat]} != defined ]]; then
(( disable_stat = 1 ))
enable stat
fi
dir=${2:+$2/}
dir=${(Q)dir}
@ -66,4 +73,5 @@ done
(( ${#listfiles} )) && listopts=(-d listfiles -l -o)
(( disable_stat )) && disable stat
return 0

View file

@ -2,7 +2,8 @@ DISTFILES_SRC='
.cvsignore .distfiles Makefile.in
META-FAQ.yo intro.ms
version.yo zmacros.yo zman.yo ztexi.yo
zsh.yo zshbuiltins.yo zshcompctl.yo zshcompsys.yo zshcompwid.yo
zsh.yo zshbuiltins.yo zshcalsys.yo
zshcompctl.yo zshcompsys.yo zshcompwid.yo
zshexpn.yo zshmisc.yo zshmodules.yo zshoptions.yo zshparam.yo
zshroadmap.yo zshzftpsys.yo zshzle.yo zshcontrib.yo zshtcpsys.yo
zsh.texi

View file

@ -45,7 +45,7 @@ TEXI2HTML = texi2html --output . --ifinfo --split=chapter
.SUFFIXES: .yo .1
# man pages to install
MAN = zsh.1 zshbuiltins.1 zshcompctl.1 zshcompwid.1 zshcompsys.1 \
MAN = zsh.1 zshbuiltins.1 zshcalsys.1 zshcompctl.1 zshcompwid.1 zshcompsys.1 \
zshcontrib.1 zshexpn.1 zshmisc.1 zshmodules.1 \
zshoptions.1 zshparam.1 zshroadmap.1 zshtcpsys.1 zshzftpsys.1 zshzle.1 \
zshall.1
@ -70,6 +70,7 @@ Zsh/mod_zprof.yo Zsh/mod_zpty.yo Zsh/mod_zselect.yo \
Zsh/mod_zutil.yo
YODLSRC = zmacros.yo zman.yo ztexi.yo Zsh/arith.yo Zsh/builtins.yo \
Zsh/calsys.yo \
Zsh/compat.yo Zsh/compctl.yo Zsh/compsys.yo Zsh/compwid.yo Zsh/cond.yo \
Zsh/contrib.yo Zsh/exec.yo Zsh/expn.yo \
Zsh/filelist.yo Zsh/files.yo Zsh/func.yo Zsh/grammar.yo Zsh/manual.yo \
@ -175,6 +176,8 @@ zsh.1 zshall.1: Zsh/intro.yo Zsh/metafaq.yo Zsh/invoke.yo Zsh/files.yo \
zshbuiltins.1: Zsh/builtins.yo
zshcalsys.1: Zsh/calsys.yo
zshcompctl.1: Zsh/compctl.yo
zshcompwid.1: Zsh/compwid.yo

View file

@ -1,6 +1,6 @@
DISTFILES_SRC='
.cvsignore .distfiles
arith.yo builtins.yo compat.yo compctl.yo compsys.yo compwid.yo
arith.yo builtins.yo calsys.yo compat.yo compctl.yo compsys.yo compwid.yo
cond.yo exec.yo expn.yo filelist.yo files.yo func.yo grammar.yo
index.yo intro.yo invoke.yo jobs.yo manual.yo metafaq.yo mod_cap.yo
mod_clone.yo mod_compctl.yo mod_complete.yo mod_complist.yo

547
Doc/Zsh/calsys.yo Normal file
View file

@ -0,0 +1,547 @@
texinode(Calendar Function System)(TCP Function System)(Zsh Modules)(Top)
chapter(Calendar Function System)
cindex(calendar function system)
cindex(zsh/datetime, function system based on)
sect(Description)
The shell is supplied with a series of functions to replace and enhance the
traditional Unix tt(calendar) programme, which warns the user of imminent
or future events, details of which are stored in a text file (typically
tt(calendar) in the user's home directory). The version provided here
includes a mechanism for alerting the user when an event is due.
In addition a function tt(age) is provided that can be used in a glob
qualifier; it allows files to be selected based on their modification
times.
The format of the tt(calendar) file and the dates used there in and in
the tt(age) function are described first, then the functions that can
be called to examine and modify the tt(calendar) file.
The functions here depend on the availability of the tt(zsh/datetime)
module which is usually installed with the shell. The library function
tt(strptime+LPAR()RPAR()) must be available; it is present on most recent
operating systems.
startmenu()
menu(Calendar File and Date Formats)
menu(Calendar System User Functions)
menu(Calendar Styles)
menu(Calendar Utility Functions)
menu(Calendar Bugs)
endmenu()
texinode(Calendar File and Date Formats)(Calendar System User Functions)()(Calendar Function System)
sect(File and Date Formats)
subsect(Calendar File Format)
The calendar file is by default tt(~/calendar). This can be configured
by the tt(calendar-file) style, see
ifzman(the section STYLES below)\
ifnzman(noderef(Calendar Styles)). The basic format consists
of a series of separate lines, with no indentation, each including
a date and time specification followed by a description of the event.
Various enhancements to this format are supported, based on the syntax
of Emacs calendar mode. An indented line indicates a continuation line
that continues the description of the event from the preceeding line
(note the date may not be continued in this way). An initial ampersand
(tt(&)) is ignored for compatibility.
The Emacs extension that a date with no description may refer to a number
of succeeding events at different times is not supported.
Unless the tt(done-file) style has been altered, any events which
have been processed are appended to the file with the same name as the
calendar file with the suffix tt(.done), hence tt(~/calendar.done) by
default.
An example is shown below.
subsect(Date Format)
The format of the date and time is designed to allow flexibility without
admitting ambiguity. Note that there is no localization support; month and
day names must be in English (though only the first three letters are
significant) and separator characters are fixed. Furthermore, time zones
are not handled; all times are assumed to be local.
It is recommended that, rather than exploring the intricacies of the
system, users find a date format that is natural to them and stick to it.
This will avoid unexpected effects. Various key facts should be noted.
startitemize()
itemiz(In particular, note the confusion between
var(month)tt(/)var(day)tt(/)var(year) and
var(day)tt(/)var(month)tt(/)var(year) when the month is numeric; these
formats should be avoided if at all possible. Many alternatives are
available.)
itemiz(The year must be given in full to avoid confusion, and only years
from 1900 to 2099 inclusive are matched.)
enditemize()
The following give some obvious examples; users finding here
a format they like and not subject to vagaries of style may skip
the full description. As dates and times are matched separately
(even though the time may be embedded in the date), any date format
may be mixed with any format for the time of day provide the
separators are clear (whitespace, colons, commas).
example(2007/04/03 13:13
2007/04/03:13:13
2007/04/03 1:13 pm
3rd April 2007, 13:13
April 3rd 2007 1:13 p.m.
Apr 3, 2007 13:13
Tue Apr 03 13:13:00 2007
13:13 2007/apr/3)
More detailed rules follow.
Times are parsed and extracted before dates. They must use colons
to separate hours and minutes, though a dot is allowed before seconds
if they are present. This limits time formats to the following:
startitemize()
itemiz(var(HH)tt(:)var(MM)[tt(:)var(SS)[tt(.)var(FFFFF)]] [tt(am)|tt(pm)|tt(a.m.)|tt(p.m.)])
itemiz(var(HH)tt(:)var(MM)tt(.)var(SS)[tt(.)var(FFFFF)] [tt(am)|tt(pm)|tt(a.m.)|tt(p.m.)])
enditemize()
Here, square brackets indicate optional elements, possibly with
alternatives. Fractions of a second are recognised but ignored. For
absolute times (the normal format require by the tt(calendar) file and the
tt(age) function) a date is mandatory but a time of day is not; the time
returned is at the start of the date. One variation is allowed: if
tt(a.m.) or tt(p.m.) or one of their variants is present, an hour without a
minute is allowed, e.g. tt(3 p.m.).
Time zones are not handled, though if one is matched following a time
specification it will be removed to allow a surrounding date to be
parsed. This only happens if the format of the timezone is not too
unusual. The following are examples of forms that are understood:
example(+0100
GMT
GMT-7
CET+1CDT)
Any part of the timezone that is not numeric must have exactly three
capital letters in the name.
Dates suffer from the ambiguity between var(DD)tt(/)var(MM)tt(/)var(YYYY)
and var(MM)tt(/)var(DD)tt(/)var(YYYY). It is recommended this form is
avoided with purely numeric dates, but use of ordinals,
eg. tt(3rd/04/2007), will resolve the ambiguity as the ordinal is always
parsed as the day of the month. Years must be four digits (and the first
two must be tt(19) or tt(20)); tt(03/04/08) is not recognised. Other
numbers may have leading zeroes, but they are not required. The following
are handled:
startitemize()
itemiz(var(YYYY)tt(/)var(MM)tt(/)var(DD))
itemiz(var(YYYY)tt(-)var(MM)tt(-)var(DD))
itemiz(var(YYYY)tt(/)var(MNM)tt(/)var(DD))
itemiz(var(YYYY)tt(-)var(MNM)tt(-)var(DD))
itemiz(var(DD)[tt(th)|tt(st)|tt(rd)] var(MNM)[tt(,)] [ var(YYYY) ])
itemiz(var(MNM) var(DD)[tt(th)|tt(st)|tt(rd)][tt(,)] [ var(YYYY) ])
itemiz(var(DD)[tt(th)|tt(st)|tt(rd)]tt(/)var(MM)[tt(,)] var(YYYY))
itemiz(var(DD)[tt(th)|tt(st)|tt(rd)]tt(/)var(MM)tt(/)var(YYYY))
itemiz(var(MM)tt(/)var(DD)[tt(th)|tt(st)|tt(rd)][tt(,)] var(YYYY))
itemiz(var(MM)tt(/)var(DD)[tt(th)|tt(st)|tt(rd)]tt(/)var(YYYY))
enditemize()
Here, var(MNM) is at least the first three letters of a month name,
matched case-insensitively. The remainder of the month name may appear but
its contents are irrelevant, so janissary, febrile, martial, apricot,
maybe, junta, etc. are happily handled.
Where the year is shown as optional, the current year is assumed. There
are only two such cases, the form tt(Jun 20) or tt(14 September) (the only
two commonly occurring forms, apart from a "the" in some forms of English,
which isn't currently supported). Such dates will of course become
ambiguous in the future, so should ideally be avoided.
Times may follow dates with a colon, e.g. tt(1965/07/12:09:45); this is in
order to provide a format with no whitespace. A comma and whitespace are
allowed, e.g. tt(1965/07/12, 09:45). Currently the order of these
separators is not checked, so illogical formats such as tt(1965/07/12, :
,09:45) will also be matched. For simplicity such variations are not shown
in the list above. Otherwise, a time is only recognised as being
associated with a date if there is only whitespace in between, or if the
time was embedded in the date.
Days of the week are not scanned, but will be ignored if they occur
at the start of the date pattern only.
For example, the standard date format:
example(Fri Aug 18 17:00:48 BST 2006)
is handled by matching var(HH)tt(:)var(MM)tt(:)var(SS) and removing it
together with the matched (but unused) time zone. This leaves the following:
example(Fri Aug 18 2006)
tt(Fri) is ignored and the rest is matched according to the standard rules.
subsect(Relative Time Format)
In certain places relative times are handled. Here, a date is not allowed;
instead a combination of various supported periods are allowed, together
with an optional time. The periods must be in order from most to
least significant.
The periods handled, with possible abbreviations are:
startitem()
item(Years)(
tt(years), tt(yrs), tt(ys), tt(year), tt(yr), tt(y).
Currently a year is 365.25 days, not a calendar year.
)
item(Months)(
tt(months), tt(mons), tt(mnths), tt(mths), tt(month), tt(mon),
tt(mnth), tt(mth). Note that tt(m), tt(ms), tt(mn), tt(mns)
are ambiguous and are em(not) handled. Currently a month is a period
of 30 days rather than a calendar month.
)
item(Weeks)(
tt(weeks), tt(wks), tt(ws), tt(week), tt(wk), tt(w)
)
item(Days)(
tt(days), tt(dys), tt(ds), tt(day), tt(dy), tt(d)
)
item(Minutes)(
tt(minutes), tt(mins), tt(minute), tt(min), but em(not) tt(m),
tt(ms), tt(mn) or tt(mns)
)
item(Seconds)(
tt(seconds), tt(secs), tt(ss), tt(second), tt(sec), tt(s)
)
enditem()
Spaces between the numbers are optional, but are required between items,
although a comma may be used (with or without spaces).
Here are some examples:
example(30 years 3 months 4 days 3:42:41
14 days 5 hours
4d,10hr)
subsect(Example)
Here is an example calendar file. It uses a consistent date format,
as recommended above. The second entry has a continuation line.
example(Feb 1, 2006 14:30 Pointless bureaucratic meeting
Mar 27, 2006 11:00 Mutual recrimination and finger pointing
Bring water pistol and waterproofs
Apr 10, 2006 13:30 Even more pointless blame assignment exercise)
texinode(Calendar System User Functions)(Calendar Styles)(Calendar File and Date Formats)(Calendar Function System)
sect(User Functions)
This section describes functions that are designed to be called
directly by the user. The first part describes those functions
associated with the user's calendar; the second part describes
the use in glob qualifiers.
subsect(Calendar system functions)
startitem()
findex(calendar)
xitem(tt(calendar) [ tt(-dDsv) ] [ tt(-C) var(calfile) ] [ -n var(num) ] [ tt(-S) var(showprog) ] [ [ var(start) ] var(end) ])(
item(tt(calendar -r) [ tt(-dDrsv) ] [ tt(-C) var(calfile) ] [ -n var(num) ] [ tt(-S) var(showprog) ] [ var(start) ])(
Show events in the calendar.
With no arguments, show events from the start of today until the end of
the next working day after today. In other words, if today is Friday,
Saturday, or Sunday, show up to the end of the following Monday, otherwise
show today and tomorrow.
If var(end) is given, show events from the start of today up to the time
and date given, which is in the format described in the previous section.
Note that if this is a date the time is assumed to be midnight at the
start of the date, so that effectively this shows all events before
the given date.
var(end) may start with a tt(+), in which case the remainder of the
specification is a relative time format as described in the previous
section indicating the range of time from the start time that is to
be included.
If var(start) is also given, show events starting from that time and date.
The word tt(now) can be used to indicate the current time.
To implement an alert when events are due, include tt(calendar -s) in your
tt(~/.zshrc) file.
Options:
startitem()
item(tt(-C) var(calfile))(
Explicitly specify a calendar file instead of the value of
the tt(calendar-file) style or the the default tt(~/calendar).
)
item(tt(-d))(
Move any events that have passed from the calendar file to the
"done" file, as given by the tt(done-file) style or the default
which is the calendar file with tt(.done) appended. This option
is implied by the tt(-s) option.
)
item(tt(-D))(
Turns off the option tt(-d), even if the tt(-s) option is also present.
)
item(tt(-n) var(num), tt(-)var(num))(
Show at least var(num) events, if present in the calendar file, regardless
of the tt(start) and tt(end).
)
item(tt(-r))(
Show all the remaining options in the calendar, ignoring the given tt(end)
time. The tt(start) time is respected; any argument given is treated
as a tt(start) time.
)
item(tt(-s))(
Use the shell's tt(sched) command to schedule a timed event that
will warn the user when an event is due. Note that the tt(sched) command
only runs if the shell is at an interactive prompt; a foreground taks
blocks the scheduled task from running until it is finished.
The timed event usually runs the programme tt(calendar_show) to show
the event, as described in
ifzman(the section UTILITY FUNCTIONS below)\
ifnzman(noderef(Calendar Utility Functions)).
By default, a warning of the event is shown five minutes before it is due.
The warning period can be configured by the style tt(warn-time) or
for a single calendar entry by including tt(WARN) var(reltime) in the first
line of the entry, where var(reltime) is one of the usual relative time
formats.
It is safe to run tt(calendar -s) to reschedule an existing event
(if the calendar file has changed, for example), and also to have it
running in multiples instances of the shell since the calendar file
is locked when in use.
By default, expired events are moved to the "done" file; see the tt(-d)
option. Use tt(-D) to prevent this.
)
item(tt(-S) var(showprog))(
Explicitly specify a programme to be used for showing events instead
of the value of the tt(show-prog) style or the default tt(calendar_show).
)
item(tt(-v))(
Verbose: show more information about stages of processing.
)
enditem()
)
findex(calendar_add)
item(tt(calendar_add) var(event ...))(
Adds a single event to the calendar in the appropriate location.
Using this function ensures that the calendar file is sorted in date
and time order. It also makes special arrangments for locking
the file will it is altered. The old calendar is left in a file
with the suffix tt(.old).
)
findex(calendar_sort)
item(tt(calendar_sort))(
Sorts the calendar file into date and time order. The old calendar is
left in a file with the suffix tt(.old).
)
enditem()
subsect(Glob qualifiers)
findex(age)
The function tt(age) can be autoloaded and use separately from
the calendar system, although it uses the function tt(calendar_scandate)
for date formatting. It requires the tt(zsh/stat) builtin, which
makes available the builtin tt(stat). This may conflict with an
external programme of the same name; if it does, the builtin may be
disabled for normal operation by including the following code in
an initialization file:
example(zmodload -i zsh/stat
disable stat)
tt(age) selects files having a given modification time for use
as a glob qualifer. The format of the date is the same as that
understood by the calendar system, described in
ifzman(the section FILE AND DATE FORMATS above)\
ifnzman(noderef(Calendar File and Date Formats)).
The function can take one or two arguments, which can be supplied either
directly as command or arguments, or separately as shell parameters.
example(print *+LPAR()e:age 2006/10/04 2006/10/09:+RPAR())
The example above matches all files modified between the start of those
dates.
example(print *+LPAR()e:age 2006/10/04+RPAR())
The example above matches all files modified on that date. If the second
argument is omitted it is taken to be exactly 24 hours after the first
argument (even if the first argument contains a time).
example(print *+LPAR()e-age 2006/10/04:10:15 2006/10/04:10:45-RPAR())
The example above supplies times. Note that whitespace within the time and
date specification must be quoted to ensure tt(age) receives the correct
arguments, hence the use of the additional colon to separate the date and
time.
example(AGEREF1=2006/10/04:10:15
AGEREF2=2006/10/04:10:45
print *+LPAR()PLUS()age+RPAR())
This shows the same example before using another form of argument
passing. The dates and times in the parameters tt(AGEREF1) and tt(AGEREF2)
stay in effect until unset, but will be overridden if any argument is
passed as an explicit argument to age. Any explicit argument
causes both parameters to be ignored.
texinode(Calendar Styles)(Calendar Utility Functions)(Calendar System User Functions)(Calendar Function System)
sect(Styles)
The zsh style mechanism using the tt(zstyle) command is describe in
ifzman(zmanref(zshmodules))\
ifnzman(noderef(The zsh/zutil Module)). This is the same mechanism
used in the completion system.
The styles below are all examined in the context
tt(:datetime:)var(function)tt(:), for example tt(:datetime:calendar:).
startitem()
kindex(calendar-file)
item(tt(calendar-file))(
The location of the main calendar. The default is tt(~/calendar).
)
kindex(done-file)
item(tt(done-file))(
The location of the file to which events which have passed are appended.
The default is the calendar file location with the suffix tt(.done).
The style may be set to an empty string in which case a "done" file
will not be maintained.
)
kindex(show-prog)
item(tt(show-prog))(
The programme run by tt(calendar) for showing events. It will
be passed the start time and stop time of the events requested in seconds
since the epoch followed by the event text. Note that tt(calendar -s) uses
a start time and stop time equal to one another to indicate alerts
for specific events.
The default is the function tt(calendar_show).
)
kindex(warn-time)
item(tt(warn-time))(
The time before an event at which a warning will be displayed, if the
first line of the event does not include the text tt(EVENT) var(reltime).
The default is 5 minutes.
)
enditem()
texinode(Calendar Utility Functions)(Calendar Bugs)(Calendar Styles)(Calendar Function System)
sect(Utility functions)
startitem()
findex(calendar_lockfiles)
item(tt(calendar_lockfiles))(
Attempt to lock the files given in the argument. To prevent
problems with network file locking this is done in an ad hoc fashion
by attempting to create a symbolic link to the file with the name
var(file)tt(.lockfile). Otherwise, however, the function is not
specific to the calendar system. Three attempts are made to lock
the file before giving up.
The files locked are appended to the array tt(lockfiles), which should
be local to the caller.
If all files were successully, status zero is returned, else status one.
)
findex(calendar_read)
item(tt(calendar_read))(
This is a backend used by various other functions to parse the
calendar file, which is passed as the only argument. The array
tt(calendar_entries) is set to the list of events in the file; no
pruning is done except that ampersands are removed from the start of
the line. Each entry may contain multiple lines.
)
findex(calendar_scandate)
item(tt(calendar_scandate))(
This is a generic function to parse dates and times that may be
used separately from the calendar system. The argument is a date
or time specification as described in
ifzman(the section FILE AND DATE FORMATS above)\
ifnzman(noderef(Calendar File and Date Formats). The parameter tt(REPLY)
is set to the number of seconds since the epoch corresponding to that date
or time. By default, the date and time may occur anywhere within the given
argument.
Returns status zero if the date and time were successfully parsed,
else one.
Options:
startitem()
item(tt(-a))(
The date and time are anchored to the start of the argument; they
will not be matched if there is preceeding text.
)
item(tt(-A))(
The date and time are anchored to both the start and end of the argument;
they will not be matched if the is any other text in the argument.
)
item(tt(-d))(
Enable additional debugging output.
)
item(tt(-r))(
The arguments passed is to be parsed as a relative time.
)
item(tt(-s))(
In addition to setting tt(REPLY), set tt(REPLY2) to the remainder of
the argument after the date and time have been stripped. This is
empty if the option tt(-A) was given.
)
enditem()
)
findex(calendar_show)
item(tt(calendar_show))(
The function used by default to display events. It accepts a start time
and end time for events, both in epoch seconds, and an event description.
The event is always printed to standard output. If the command line editor
is active (which will usually be the case) the command line will be
redisplayed after the output.
If the parameter tt(DISPLAY) is set and the start and end times are
the same (indicating a scheduled event), the function uses the
command tt(xmessage) to display a window with the event details.
)
enditem()
texinode(Calendar Bugs)(Calendar Utility Functions)()(Calendar Function System)
sect(Bugs)
There is no tt(calendar_delete) function.
There is no localization support for dates and times, nor any support
for the use of time zones.
Relative periods of months and years do not take into account the variable
number of days.
Recurrent events are not yet supported.
The tt(calendar_show) function is currently hardwired to use tt(xmessage)
for displaying alerts on X Window System displays. This should be
configurable and ideally integrate better with the desktop.
tt(calendar_lockfiles) hangs the shell while waiting for a lock on a file.
If called from a scheduled task, it should instead reschedule the event
that caused it.

View file

@ -1361,10 +1361,14 @@ specified will always be completed.
kindex(file-list, completion style)
item(tt(file-list))(
This style controls whether files completed using the standard builtin
mechanism are to be listed with a long list similar to tt(ls -l)
(although note that this feature actually uses the shell module
mechanism are to be listed with a long list similar to tt(ls -l).
Note that this feature uses the shell module
tt(zsh/stat) for file information; this loads the builtin tt(stat)
which will replace any external tt(stat) executable).
which will replace any external tt(stat) executable. To avoid
this the following code can be included in an initialization file:
example(zmodload -i zsh/stat
disable stat)
The style may either be set to a true value (or `tt(all)'), or
one of the values `tt(insert)' or `tt(list)', indicating that files

View file

@ -26,6 +26,7 @@ list(em(zshcompwid) Zsh completion widgets)
list(em(zshcompsys) Zsh completion system)
list(em(zshcompctl) Zsh completion control)
list(em(zshmodules) Zsh loadable modules)
list(em(zshcalsys) Zsh built-in calendar functions)
list(em(zshtcpsys) Zsh built-in TCP functions)
list(em(zshzftpsys) Zsh built-in FTP client)
list(em(zshcontrib) Additional zsh functions and utilities)

View file

@ -34,6 +34,7 @@ menu(Completion Widgets)
menu(Completion System)
menu(Completion Using compctl)
menu(Zsh Modules)
menu(Calendar Function System)
menu(TCP Function System)
menu(Zftp Function System)
menu(User Contributions)

View file

@ -1,4 +1,4 @@
texinode(Zsh Modules)(TCP Function System)(Completion Using compctl)(Top)
texinode(Zsh Modules)(Calendar Function System)(Completion Using compctl)(Top)
chapter(Zsh Modules)
cindex(modules)
sect(Description)

View file

@ -1,4 +1,4 @@
texinode(TCP Function System)(Zftp Function System)(Zsh Modules)(Top)
texinode(TCP Function System)(Zftp Function System)(Calendar Function System)(Top)
chapter(TCP Function System)
cindex(TCP function system)
cindex(ztcp, function system based on)

View file

@ -64,6 +64,7 @@ ifnzman(includefile(Zsh/compwid.yo))
ifnzman(includefile(Zsh/compsys.yo))
ifnzman(includefile(Zsh/compctl.yo))
ifnzman(includefile(Zsh/modules.yo))
ifnzman(includefile(Zsh/calsys.yo))
ifnzman(includefile(Zsh/tcpsys.yo))
ifnzman(includefile(Zsh/zftpsys.yo))
ifnzman(includefile(Zsh/contrib.yo))
@ -81,6 +82,7 @@ source(zshcompwid)
source(zshcompsys)
source(zshcompctl)
source(zshmodules)
source(zshcalsys)
source(zshtcpsys)
source(zshzftpsys)
source(zshcontrib)

3
Doc/zshcalsys.yo Normal file
View file

@ -0,0 +1,3 @@
manpage(ZSHCALSYS)(1)(date())(zsh version())
manpagename(zshcalsys)(zsh calendar system)
includefile(Zsh/calsys.yo)

View file

@ -0,0 +1,6 @@
DISTFILES_SRC='
.distfiles
age
calendar calendar_add calendar_lockfiles calendar_read
calendar_scandate calendar_show calendar_sort
'

73
Functions/Calendar/age Normal file
View file

@ -0,0 +1,73 @@
# Match the age of a file, for use as a glob qualifer. Can
# take one or two arguments, which can be supplied by one of two
# ways (always the same for both arguments):
#
# print *(e:age 2006/10/04 2006/10/09:)
#
# Match all files modified between the start of those dates.
#
# print *(e:age 2006/10/04)
#
# Match all files modified on that date. If the second argument is
# omitted it is taken to be exactly 24 hours after the first argument
# (even if the first argument contains a time).
#
# print *(e:age 2006/10/04:10:15 2006/10/04:10:45)
#
# Supply times. All the time and formats handled by calendar_scandate
# are allowed, but whitespace must be quoted to ensure age receives
# the correct arguments.
#
# AGEREF1=2006/10/04:10:15
# AGEREF2=2006/10/04:10:45
# print *(+age)
#
# The same example using the other form of argument passing. The
# dates stay in effect until unset, but will be overridden if
# any argument is passed in the first format.
emulate -L zsh
integer mystat disable_stat
zmodload -i zsh/stat
# Allow the builtin stat to be hidden.
zmodload -i zsh/parameter
if [[ $builtins[stat] != defined ]]; then
(( disable_stat = 1 ))
enable stat
fi
autoload -U calendar_scandate
local -a vals
[[ -e $REPLY ]] || return 1
stat -A vals +mtime $REPLY || return 1
if (( $# >= 1 )); then
local AGEREF=$1
# if 1 argument given, never use globally defined AGEREF2
local AGEREF2=$2
fi
integer mtime=$vals[1] date1 date2
local REPLY
if calendar_scandate $AGEREF; then
date1=$REPLY
if [[ -n $AGEREF2 ]] && calendar_scandate $AGEREF2; then
date2=$REPLY
else
(( date2 = date1 + 24 * 60 * 60 ))
fi
(( date1 <= mtime && mtime <= date2 ))
else
mystat=1
fi
# If the builtin stat was previously disabled, disable it again.
(( disable_stat )) && disable stat
return $mystat

356
Functions/Calendar/calendar Normal file
View file

@ -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

View file

@ -0,0 +1,69 @@
#!/bin/env zsh
# All arguments are joined with spaces and inserted into the calendar
# file at the appropriate point.
#
# While the function compares the date of the new entry with dates in the
# existing calendar file, it does not do any sorting; it inserts the new
# entry before the first existing entry with a later date and time.
emulate -L zsh
setopt extendedglob
local calendar newfile REPLY lastline
local -a calendar_entries lockfiles
integer newdate done rstat
autoload -U calendar_{read,lockfiles}
# Read the calendar file from the calendar-file style
zstyle -s ':datetime:calendar_add:' calendar-file calendar ||
calendar=~/calendar
newfile=$calendar.new.$HOST.$$
if ! calendar_scandate -a "$*"; then
print "$0: failed to parse date/time" >&2
return 1
fi
(( newdate = $REPLY ))
# $calendar doesn't necessarily exist yet.
# start of block for following always to clear up lockfiles.
{
calendar_lockfiles $calendar || return 1
if [[ -f $calendar ]]; then
calendar_read $calendar
{
for line in $calendar_entries; do
if (( ! done )) && calendar_scandate -a $line && (( REPLY > newdate )); then
print -r -- "$*"
(( done = 1 ))
elif [[ $REPLY -eq $newdate && $line = "$*" ]]; then
(( done = 1 ))
fi
print -r -- $line
done
(( done )) || print -r -- "$*"
} >$newfile
if ! mv $calendar $calendar.old; then
print "Couldn't back up $calendar to $calendar.old.
New calendar left in $newfile." >&2
(( rstat = 1 ))
fi
else
print -r -- $line >$newfile
fi
if (( !rstat )) && ! mv $newfile $calendar; then
print "Failed to rename $newfile to $calendar.
Old calendar left in $calendar.old." >&2
(( rstat = 1 ))
fi
} always {
(( ${#lockfiles} )) && rm -f $lockfiles
}
return $rstat

View file

@ -0,0 +1,43 @@
# Lock the given files.
# Append the names of lockfiles to the array lockfiles.
local file lockfile msgdone
# Number of attempts to lock a file. Probably not worth stylising.
integer lockattempts=3
# The lockfile name is not stylised: it has to be a fixed
# derivative of the main fail.
for file; do
lockfile=$file.lockfile
for (( i = 0; i < lockattempts; i++ )); do
if ln -s $file $lockfile >/dev/null 2>&1; then
lockfiles+=($lockfile)
break
fi
if zle && [[ -z $msgdone ]]; then
msgdone="${lockfile}: waiting to acquire lock"
zle -M $msgdone
fi
sleep 1
done
if [[ -n $msgdone ]]; then
zle -M ${msgdone//?/ }
msgdone=
fi
if [[ ${lockfiles[-1]} != $lockfile ]]; then
msgdone="Failed to lock $file; giving up after $lockattempts attempts.
Another instance of calendar may be using it.
Delete $lockfiles if you believe this to be an error."
if zle; then
zle -M $msgdone
else
print $msgdone >&2
fi
# The parent should take action to delete any lockfiles
# already locked. Typically this won't be necessary, since
# we will always lock the main calendar file first.
return 1
fi
done
return 0

View file

@ -0,0 +1,35 @@
# Utility for "calendar" to read entries into the array calendar_entries.
# This should be local to the caller.
# The only argument is the file to read. We expect options etc. to
# be correct.
#
# This is based on Emacs calendar syntax, which has two implications:
# - Lines beginning with whitespace are continuation lines.
# Hence we have to read the entire file first to determine entries.
# - Lines beginning with "&" are inhibited from producing marks in
# Emacs calendar window. This is irrelevant to us, so we
# we simply remove leading ampersands. This is necessary since
# we expect the date to start at the beginning of the line.
#
# TODO: Emacs has some special handling for entries where the first line
# has only the date and continuation lines indicate times. Actually,
# it doesn't parse the times as far as I can see, but if we want to
# handle that format sensibly we would need to here. It could
# be tricky to get right.
local calendar=$1 line
local -a lines
lines=(${(f)"$(<$calendar)"})
calendar_entries=()
# ignore blank lines
for line in $lines; do
if [[ $line = [[:space:]]* ]]; then
if (( ${#calendar_entries} )); then
calendar_entries[-1]+=$'\n'$line
fi
else
calendar_entries+=(${line##\&})
fi
done

View file

@ -0,0 +1,519 @@
# Scan a line for various common date and time formats.
# Set REPLY to the number of seconds since the epoch at which that
# time occurs. The time does not need to be matched; this will
# produce midnight at the start of the date.
#
# Absolute times
#
# The rules below are fairly complicated, to allow any natural (and
# some highly unnatural but nonetheless common) combination of
# time and date used by English speakers. It is recommended that,
# rather than exploring the intricacies of the system, users find
# a date format that is natural to them and stick to it. This
# will avoid unexpected effects. Various key facts should be noted,
# explained in more detail below:
#
# - In particular, note the confusion between month/day/year and
# day/month/year when the month is numeric; this format should be
# avoided if at all possible. Many alternatives are available.
# - However, there is currently no localization support, so month
# names must be English (though only the first three letters are required).
# The same applies to days of the week if they occur (they are not useful).
# - The year must be given in full to avoid confusion, and only years
# from 1900 to 2099 inclusive are matched.
# - Although timezones are parsed (complicated formats may not be recognized),
# they are then ignored; no time adjustment is made.
#
# The following give some obvious examples; users finding here
# a format they like and not subject to vagaries of style may skip
# the full description. As dates and times are matched separately
# (even though the time may be embedded in the date), any date format
# may be mixed with any format for the time of day provide the
# separators are clear (whitespace, colons, commas).
# 2007/04/03 13:13
# 2007/04/03:13:13
# 2007/04/03 1:13 pm
# 3rd April 2007, 13:13
# April 3rd 2007 1:13 p.m.
# Apr 3, 2007 13:13
# Tue Apr 03 13:13:00 2007
# 13:13 2007/apr/3
#
# Times are parsed and extracted before dates. They must use colons
# to separate hours and minutes, though a dot is allowed before seconds
# if they are present. This limits time formats to
# HH:MM[:SS[.FFFFF]] [am|pm|a.m.|p.m.]
# HH:MM.SS[.FFFFF] [am|pm|a.m.|p.m.]
# in which square brackets indicate optional elements, possibly with
# alternatives. Fractions of a second are recognised but ignored.
# Unless -r is given (see below), a date is mandatory but a time of day is
# not; the time returned is at the start of the date.
#
# Time zones are not handled, though if one is matched following a time
# specification it will be removed to allow a surrounding date to be
# parsed. This only happens if the format of the timezone is not too
# wacky:
# +0100
# GMT
# GMT-7
# CET+1CDT
# etc. are all understood, but any part of the timezone that is not numeric
# must have exactly three capital letters in the name.
#
# Dates suffer from the ambiguity between DD/MM/YYYY and MM/DD/YYYY. It is
# recommended this form is avoided with purely numeric dates, but use of
# ordinals, eg. 3rd/04/2007, will resolve the ambiguity as the ordinal is
# always parsed as the day of the month. Years must be four digits (and
# the first two must be 19 or 20); 03/04/08 is not recognised. Other
# numbers may have leading zeroes, but they are not required. The
# following are handled:
# YYYY/MM/DD
# YYYY-MM-DD
# YYYY/MNM/DD
# YYYY-MNM-DD
# DD[th|st|rd] MNM[,] YYYY
# DD[th|st|rd] MNM[,] current year assumed
# MNM DD[th|st|rd][,] YYYY
# MNM DD[th|st|rd][,] current year assumed
# DD[th|st|rd]/MM[,] YYYY
# DD[th|st|rd]/MM/YYYY
# MM/DD[th|st|rd][,] YYYY
# MM/DD[th|st|rd]/YYYY
# Here, MNM is at least the first three letters of a month name,
# matched case-insensitively. The remainder of the month name may appear but
# its contents are irrelevant, so janissary, febrile, martial, apricot,
# etc. are happily handled.
#
# Note there are only two cases that assume the current year, the
# form "Jun 20" or "14 September" (the only two commonly occurring
# forms, apart from a "the" in some forms of English, which isn't
# currently supported). Such dates will of course become ambiguous
# in the future, so should ideally be avoided.
#
# Times may follow dates with a colon, e.g. 1965/07/12:09:45; this
# is in order to provide a format with no whitespace. A comma
# and whitespace are allowed, e.g. "1965/07/12, 09:45".
# Currently the order of these separators is not checked, so
# illogical formats such as "1965/07/12, : ,09:45" will also
# be matched. Otherwise, a time is only recognised as being associated
# with a date if there is only whitespace in between, or if the time
# was embedded in the date.
#
# Days of the week are not scanned, but will be ignored if they occur
# at the start of the date pattern only.
#
# For example, the standard date format:
# Fri Aug 18 17:00:48 BST 2006
# is handled by matching HH:MM:SS and removing it together with the
# matched (but unused) time zone. This leaves the following:
# Fri Aug 18 2006
# "Fri" is ignored and the rest is matched according to the sixth of
# the standard rules.
#
# Relative times
# ==============
#
# The option -r allows a relative time. Years (or ys, yrs, or without s),
# months (or mths, mons, mnths, months, or without s --- "m", "ms" and
# "mns" are ambiguous and are not handled), weeks (or ws, wks, or without
# s) and days (or ds, dys, days, or without s), hours (or hs, hrs, with or
# without s), minutes (or mins, with or without s) and seconds (or ss,
# secs, with or without s) are understood. Spaces between the numbers
# are optional, but are required between items, although a comma
# may be used (with or without spaces).
#
# Note that a year here is 365.25 days and a month is 30 days. TODO:
# improve this by passing down base time and adjusting. (This will
# be crucial for events repeating monthly.) TODO: it then makes
# sense to make PERIODly = 1 PERIOD (also for PERIOD = dai!)
#
# This allows forms like:
# 30 years 3 months 4 days 3:42:41
# 14 days 5 hours
# 4d,10hr
# In this case absolute dates are ignored.
emulate -L zsh
setopt extendedglob
zmodload -i zsh/datetime || return 1
# separator characters before time or between time and date
# allow , - or : before the time: this allows spaceless but still
# relatively logical dates like 2006/09/19:14:27
# don't allow / before time ! the above
# is not 19 hours 14 mins and 27 seconds after anything.
local tschars="[-,:[:space:]]"
# start pattern for time when anchored
local tspat_anchor="(${tschars}#)"
# ... when not anchored
local tspat_noanchor="(|*${tschars})"
# separator characters between elements. comma is fairly
# natural punctuation; otherwise only allow whitespace.
local schars="[.,[:space:]]"
local daypat="${schars}#(sun|mon|tue|wed|thu|fri|sat)[a-z]#${schars}#"
# Start pattern for date: treat , as space for simplicity. This
# is illogical at the start but saves lots of minor fiddling later.
# Date start pattern when anchored at the start.
# We need to be able to ignore the day here, although (for consistency
# with the unanchored case) we don't remove it until later.
# (The problem in the other case is that matching anything before
# the day of the week is greedy, so the day of the week gets ignored
# if it's optional.)
local dspat_anchor="(|(#B)${daypat}(#b)${schars}#)"
# Date start pattern when not anchored at the start.
local dspat_noanchor="(|*${schars})"
# end pattern for relative times: similar remark about use of $schars.
local repat="(|s)(|${schars}*)"
# not locale-dependent! I don't know how to get the months out
# of the system for the purpose of finding out where they occur.
# We may need some completely different heuristic.
local monthpat="(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)[a-z]#"
# days, not handled but we need to ignore them. also not localized.
integer year month day hour minute second
local opt line orig_line mname MATCH MBEGIN MEND tz
local -a match mbegin mend
# Flags that we found a date or a time (maybe a relative time)
integer date_found time_found
# Indices of positions of start and end of time and dates found.
# These are actual character indices as zsh would normally use, i.e.
# line[time_start,time_end] is the string for the time.
integer time_start time_end date_start date_end
integer anchor anchor_end debug relative reladd setvar
while getopts "aAdrs" opt; do
case $opt in
(a)
# anchor
(( anchor = 1 ))
;;
(A)
# anchor at end, too
(( anchor = 1, anchor_end = 1 ))
;;
(d)
# enable debug output
(( debug = 1 ))
;;
(r)
(( relative = 1 ))
;;
(s)
(( setvar = 1 ))
;;
(*)
return 1
;;
esac
done
shift $(( OPTIND - 1 ))
line=$1 orig_line=$1
local dspat tspat
if (( anchor )); then
# Anchored at the start.
dspat=$dspat_anchor
if (( relative )); then
tspat=$tspat_anchor
else
# We'll test later if the time is associated with the date.
tspat=$tspat_noanchor
fi
else
dspat=$dspat_noanchor
tspat=$tspat_noanchor
fi
# Look for a time separately; we need colons for this.
case $line in
# with seconds, am/pm: don't match / in front.
((#ibm)${~tspat}(<0-12>):(<0-59>)[.:]((<0-59>)(.<->|))[[:space:]]#([ap])(|.)[[:space:]]#m(.|[[:space:]]|(#e))(*))
hour=$match[2]
minute=$match[3]
second=$match[5]
[[ $match[7] = (#i)p ]] && (( hour <= 12 )) && (( hour += 12 ))
time_found=1
;;
# no seconds, am/pm
((#ibm)${~tspat}(<0-12>):(<0-59>)[[:space:]]#([ap])(|.)[[:space:]]#m(.|[[:space:]]|(#e))(*))
hour=$match[2]
minute=$match[3]
[[ $match[4] = (#i)p ]] && (( hour <= 12 )) && (( hour += 12 ))
time_found=1
;;
# no colon, even, but a.m./p.m. indicator
((#ibm)${~tspat}(<0-12>)[[:space:]]#([ap])(|.)[[:space:]]#m(.|[[:space:]]|(#e))(*))
hour=$match[2]
minute=0
[[ $match[3] = (#i)p ]] && (( hour <= 12 )) && (( hour += 12 ))
time_found=1
;;
# 24 hour clock, with seconds
((#ibm)${~tspat}(<0-24>):(<0-59>)[.:]((<0-59>)(.<->|))(*))
hour=$match[2]
minute=$match[3]
second=$match[5]
time_found=1
;;
# 24 hour clock, no seconds
((#ibm)${~tspat}(<0-24>):(<0-59>)(*))
hour=$match[2]
minute=$match[3]
time_found=1
;;
esac
(( hour == 24 )) && hour=0
if (( time_found )); then
# time was found
time_start=$mbegin[2]
time_end=$mend[-2]
# Remove the timespec because it may be in the middle of
# the date (as in the output of "date".
# There may be a time zone, too, which we don't yet handle.
# (It's not in POSIX strptime() and libraries don't support it well.)
# This attempts to remove some of the weirder forms.
if [[ $line[$time_end+1,-1] = (#b)[[:space:]]#([A-Z][A-Z][A-Z]|[-+][0-9][0-9][0-9][0-9])([[:space:]]|(#e))* || \
$line[$time_end+1,-1] = (#b)[[:space:]]#([A-Z][A-Z][A-Z](|[-+])<0-12>)([[:space:]]|(#e))* || \
$line[$time_end+1,-1] = (#b)[[:space:]]#([A-Z][A-Z][A-Z](|[-+])<0-12>[A-Z][A-Z][A-Z])([[:space:]]|(#e))* ]]; then
(( time_end += ${mend[-1]} ))
tz=$match[1]
fi
line=$line[1,time_start-1]$line[time_end+1,-1]
(( debug )) && print "line after time: $line"
fi
if (( relative == 0 )); then
# Date.
case $line in
# Look for YEAR[-/.]MONTH[-/.]DAY
((#bi)${~dspat}((19|20)[0-9][0-9])[-/](<1-12>)[-/](<1-31>)*)
year=$match[2]
month=$match[4]
day=$match[5]
date_start=$mbegin[2] date_end=$mend[5]
date_found=1
;;
# Same with month name
((#bi)${~dspat}((19|20)[0-9][0-9])[-/]${~monthpat}[-/](<1-31>)*)
year=$match[2]
mname=$match[4]
day=$match[5]
date_start=$mbegin[2] date_end=$mend[5]
date_found=1
;;
# Look for DAY[th/st/rd] MNAME[,] YEAR
((#bi)${~dspat}(<1-31>)(|th|st|rd)[[:space:]]##${~monthpat}(|,)[[:space:]]##((19|20)[0-9][0-9])*)
day=$match[2]
mname=$match[4]
year=$match[6]
date_start=$mbegin[2] date_end=$mend[6]
date_found=1
;;
# Look for MNAME DAY[th/st/rd][,] YEAR
((#bi)${~dspat}${~monthpat}[[:space:]]##(<1-31>)(|th|st|rd)(|,)[[:space:]]##((19|20)[0-9][0-9])*)
mname=$match[2]
day=$match[3]
year=$match[6]
date_start=$mbegin[2] date_end=$mend[6]
date_found=1
;;
# Look for DAY[th/st/rd] MNAME; assume current year
((#bi)${~dspat}(<1-31>)(|th|st|rd)[[:space:]]##${~monthpat}(|,)([[:space:]]##*|))
day=$match[2]
mname=$match[4]
strftime -s year "%Y" $EPOCHSECONDS
date_start=$mbegin[2] date_end=$mend[5]
date_found=1
;;
# Look for MNAME DAY[th/st/rd]; assume current year
((#bi)${~dspat}${~monthpat}[[:space:]]##(<1-31>)(|th|st|rd)(|,)([[:space:]]##*|))
mname=$match[2]
day=$match[3]
strftime -s year "%Y" $EPOCHSECONDS
date_start=$mbegin[2] date_end=$mend[5]
date_found=1
;;
# Now it gets a bit ambiguous.
# Look for DAY[th/st/rd][/]MONTH[/ ,]YEAR
((#bi)${~dspat}(<1-31>)(|th|st|rd)/(<1-12>)((|,)[[:space:]]##|/)((19|20)[0-9][0-9])*)
day=$match[2]
month=$match[4]
year=$match[7]
date_start=$mbegin[2] date_end=$mend[7]
date_found=1
;;
# Look for MONTH[/]DAY[th/st/rd][/ ,]YEAR
((#bi)${~dspat}(<1-12>)/(<1-31>)(|th|st|rd)((|,)[[:space:]]##|/)((19|20)[0-9][0-9])*)
month=$match[2]
day=$match[3]
year=$match[7]
date_start=$mbegin[2] date_end=$mend[7]
date_found=1
;;
esac
fi
if (( date_found )); then
# date found
# see if there's a day at the start
if [[ ${line[1,$date_start-1]} = (#bi)${~daypat} ]]; then
date_start=$mbegin[1]
fi
line=${line[1,$date_start-1]}${line[$date_end+1,-1]}
if (( time_found )); then
# If we found a time, it must be associated with the date,
# or we can't use it. Since we removed the time from the
# string to find the date, however, it's complicated to
# know where both were found. Reconstruct the date indices of
# the original string.
if (( time_start <= date_start )); then
# Time came before start of date; add length in.
(( date_start += time_end - time_start + 1 ))
fi
if (( time_start <= date_end )); then
(( date_end += time_end - time_start + 1 ))
fi
if (( time_end + 1 < date_start )); then
# If time wholly before date, OK if only separator characters
# in between. (This allows some illogical stuff with commas
# but that's probably not important.)
if [[ ${orig_line[time_end+1,date_start-1]} != ${~schars}# ]]; then
# Clearly this can't work if anchor is set. In principle,
# we could match the date and ignore the time if it wasn't.
# However, that seems dodgy.
return 1
else
# Form massaged line by removing the entire date/time chunk.
line="${orig_line[1,time_start-1]}${orig_line[date_end+1,-1]}"
fi
elif (( date_end + 1 < time_start )); then
# If date wholly before time, OK if only time separator characters
# in between. This allows 2006/10/12:13:43 etc.
if [[ ${orig_line[date_end+1,time_start-1]} != ${~tschars}# ]]; then
# Here, we assume the time is associated with something later
# in the line. This is pretty much inevitable for the sort
# of use we are expecting. For example,
# 2006/10/24 Meeting from early, may go on till 12:00.
# or with some uses of the calendar system,
# 2006/10/24 MR 1 Another pointless meeting WARN 01:00
# The 01:00 says warn an hour before, not that the meeting starts
# at 1 am. About the only safe way round would be to force
# a time to be present, but that's not how the traditional
# calendar programme works.
#
# Hence we need to reconstruct.
(( time_found = 0, hour = 0, minute = 0, second = 0 ))
line="${orig_line[1,date_start-1]}${orig_line[date_end+1,-1]}"
else
# As above.
line="${orig_line[1,date_start-1]}${orig_line[time_end+1,-1]}"
fi
fi
if (( debug )); then
print "Time string: $time_start,$time_end:" \
"'$orig_line[time_start,time_end]'"
print "Date string: $date_start,$date_end:" \
"'$orig_line[date_start,date_end]'"
print "Remaining line: '$line'"
fi
fi
fi
if (( relative )); then
if [[ $line = (#bi)${~dspat}(<->)[[:blank:]]#(y|yr|year)${~repat} ]]; then
(( reladd += ((365*4+1) * 24 * 60 * 60 * ${match[2]} + 1) / 4 ))
line=${line[1,$mbegin[2]-1]}${line[$mend[4]+1,-1]}
time_found=1
fi
if [[ $line = (#bi)${~dspat}(<->)[[:blank:]]#(mth|mon|mnth|month)${~repat} ]]; then
(( reladd += 30 * 24 * 60 * 60 * ${match[2]} ))
line=${line[1,$mbegin[2]-1]}${line[$mend[4]+1,-1]}
time_found=1
fi
if [[ $line = (#bi)${~dspat}(<->)[[:blank:]]#(w|wk|week)${~repat} ]]; then
(( reladd += 7 * 24 * 60 * 60 * ${match[2]} ))
line=${line[1,$mbegin[2]-1]}${line[$mend[4]+1,-1]}
time_found=1
fi
if [[ $line = (#bi)${~dspat}(<->)[[:blank:]]#(d|dy|day)${~repat} ]]; then
(( reladd += 24 * 60 * 60 * ${match[2]} ))
line=${line[1,$mbegin[2]-1]}${line[$mend[4]+1,-1]}
time_found=1
fi
if [[ $line = (#bi)${~dspat}(<->)[[:blank:]]#(h|hr|hour)${~repat} ]]; then
(( reladd += 60 * 60 * ${match[2]} ))
line=${line[1,$mbegin[2]-1]}${line[$mend[4]+1,-1]}
time_found=1
fi
if [[ $line = (#bi)${~dspat}(<->)[[:blank:]]#(min|minute)${~repat} ]]; then
(( reladd += 60 * ${match[2]} ))
line=${line[1,$mbegin[2]-1]}${line[$mend[4]+1,-1]}
time_found=1
fi
if [[ $line = (#bi)${~dspat}(<->)[[:blank:]]#(s|sec|second)${~repat} ]]; then
(( reladd += ${match[2]} ))
line=${line[1,$mbegin[2]-1]}${line[$mend[4]+1,-1]}
time_found=1
fi
fi
if (( relative )); then
# If no date was found, we're in trouble unless we found a time.
if (( time_found )); then
if (( anchor_end )); then
# must be left with only separator characters
if [[ $line != ${~schars}# ]]; then
return 1
fi
fi
(( REPLY = reladd + (hour * 60 + minute) * 60 + second ))
[[ -n $setvar ]] && REPLY2=$line
return 0
fi
return 1
elif (( ! date_found )); then
return 1
fi
if (( anchor_end )); then
# must be left with only separator characters
if [[ $line != ${~schars}# ]]; then
return 1
fi
fi
local fmt nums
if [[ -n $mname ]]; then
fmt="%Y %b %d %H %M %S"
nums="$year $mname $day $hour $minute $second"
else
fmt="%Y %m %d %H %M %S"
nums="$year $month $day $hour $minute $second"
fi
strftime -s REPLY -r $fmt $nums
[[ -n $setvar ]] && REPLY2=$line
return 0

View file

@ -0,0 +1,24 @@
integer start=$1 stop=$2
shift 2
[[ -o zle ]] && zle -I
print -r "$*"
local -a cmd
zmodload -i zsh/parameter || return
# Use xmessage to display the message if the start and stop time
# are the same, indicating we have been scheduled to display it.
# Don't do this if there's already an xmessage for the same user.
# HERE: this should be configurable and we should be able to do
# better if xmessage isn't available, e.g. wish.
if [[ -n $DISPLAY && $start -eq $stop ]]; then
if [[ -n ${commands[xmessage]} ]]; then
cmd=(xmessage -center)
fi
if [[ -n $cmd[0] ]] &&
! ps -u$UID | grep $cmd[0] >/dev/null 2>&1; then
# turn off job control for this
($cmd "$*" &)
fi
fi

View file

@ -0,0 +1,67 @@
emulate -L zsh
setopt extendedglob
autoload -U calendar_{read,scandate,lockfiles}
local calendar line REPLY new lockfile
local -a calendar_entries
local -a times lines_sorted lines_unsorted lines_failed lockfiles
integer i
# Read the calendar file from the calendar-file style
zstyle -s ':datetime:calendar:' calendar-file calendar || calendar=~/calendar
# Start block for "always" to handle lockfile
{
calendar_lockfiles $calendar || return 1
new=$calendar.new.$$
calendar_read $calendar
if [[ ${#calendar_entries} -eq 0 || \
( ${#calendar_entries} -eq 1 && -z $calendar_entries[1] ) ]]; then
return 0
fi
for line in $calendar_entries; do
if calendar_scandate -a $line; then
lines_unsorted+=("${(l.16..0.)REPLY}:$line")
else
lines_failed+=($line)
fi
done
if (( ${#lines_unsorted} )); then
lines_sorted=(${${(o)lines_unsorted}##[0-9]##:})
fi
{
for line in "${lines_failed[@]}"; do
print "$line # BAD DATE"
done
(( ${#lines_sorted} )) && print -l "${lines_sorted[@]}"
} > $new
if [[ ! -s $new ]]; then
print "Writing to $new failed."
return 1
elif (( ${#lines_failed} )); then
print "Warning: lines with date that couldn't be parsed.
Output (with unparseable dates marked) left in $new"
return 1
fi
if ! mv $calendar $calendar.old; then
print "Couldn't back-up $calendar to $calendar.old.
New calendar left in $new"
return 1
fi
if ! mv $new $calendar; then
print "Failed to rename $new to $calendar.
Old calendar left in $calendar.old"
return 1
fi
print "Old calendar left in $calendar.old"
} always {
(( ${#lockfiles} )) && rm -rf $lockfiles
}

View file

@ -3,6 +3,7 @@ name=zsh/datetime
link=either
load=no
functions='Functions/Calendar/*'
autobins="strftime"
objects="datetime.o"