mirror of
https://salsa.debian.org/dskoll/remind.git
synced 2026-04-16 14:28:40 +02:00
Compare commits
38 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8eb40ae748 | ||
|
|
89184f1d0f | ||
|
|
e899c790b9 | ||
|
|
bd6d695020 | ||
|
|
20d4626a71 | ||
|
|
8ff94c5031 | ||
|
|
ee185a0eeb | ||
|
|
06f8932efd | ||
|
|
1dc627148c | ||
|
|
3cdde5351f | ||
|
|
6e93b8a73d | ||
|
|
267e8533cf | ||
|
|
d3bfb0a28f | ||
|
|
5a31bc7058 | ||
|
|
746bde71bd | ||
|
|
b274ac635c | ||
|
|
9e0a74e583 | ||
|
|
0f782f7697 | ||
|
|
8efde3e9af | ||
|
|
3bf3137dc4 | ||
|
|
63ec32d28d | ||
|
|
d2f4177cdb | ||
|
|
1d958fb7a8 | ||
|
|
fcd580d42e | ||
|
|
34dab68805 | ||
|
|
216dd03922 | ||
|
|
5eef9ac621 | ||
|
|
6b798d5f7c | ||
|
|
22ccce0934 | ||
|
|
fe2af14952 | ||
|
|
8e99ed27e7 | ||
|
|
bb12362cc8 | ||
|
|
1bfc630a64 | ||
|
|
987983f8ae | ||
|
|
657a6118aa | ||
|
|
43e7e6ec7f | ||
|
|
b8b3c19fbf | ||
|
|
69298c96a5 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -16,6 +16,7 @@ man/remind.1
|
||||
man/tkremind.1
|
||||
pm_to_blib
|
||||
rem2html/Makefile
|
||||
rem2html/rem2html
|
||||
rem2html/rem2html.1
|
||||
rem2pdf/Makefile.PL
|
||||
rem2pdf/Makefile.old
|
||||
|
||||
2
Makefile
2
Makefile
@@ -44,7 +44,7 @@ test:
|
||||
@$(MAKE) -C src -s test
|
||||
|
||||
distclean: clean
|
||||
rm -f config.cache config.log config.status src/Makefile src/config.h tests/test.out www/Makefile rem2pdf/Makefile.top rem2pdf/Makefile.old rem2pdf/Makefile rem2pdf/Makefile.PL rem2pdf/bin/rem2pdf
|
||||
rm -f config.cache config.log config.status src/Makefile src/config.h tests/test.out www/Makefile rem2pdf/Makefile.top rem2pdf/Makefile.old rem2pdf/Makefile rem2pdf/Makefile.PL rem2pdf/bin/rem2pdf rem2html/rem2html
|
||||
|
||||
src/Makefile: src/Makefile.in
|
||||
./configure
|
||||
|
||||
17
configure
vendored
17
configure
vendored
@@ -4034,6 +4034,12 @@ then :
|
||||
printf "%s\n" "#define HAVE_LANGINFO_H 1" >>confdefs.h
|
||||
|
||||
fi
|
||||
ac_fn_c_check_header_compile "$LINENO" "sys/inotify.h" "ac_cv_header_sys_inotify_h" "$ac_includes_default"
|
||||
if test "x$ac_cv_header_sys_inotify_h" = xyes
|
||||
then :
|
||||
printf "%s\n" "#define HAVE_SYS_INOTIFY_H 1" >>confdefs.h
|
||||
|
||||
fi
|
||||
|
||||
|
||||
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether struct tm is in sys/time.h or time.h" >&5
|
||||
@@ -4211,14 +4217,20 @@ then :
|
||||
printf "%s\n" "#define HAVE_INITGROUPS 1" >>confdefs.h
|
||||
|
||||
fi
|
||||
ac_fn_c_check_func "$LINENO" "inotify_init1" "ac_cv_func_inotify_init1"
|
||||
if test "x$ac_cv_func_inotify_init1" = xyes
|
||||
then :
|
||||
printf "%s\n" "#define HAVE_INOTIFY_INIT1 1" >>confdefs.h
|
||||
|
||||
fi
|
||||
|
||||
|
||||
VERSION=04.02.09
|
||||
VERSION=04.03.00
|
||||
|
||||
|
||||
|
||||
|
||||
ac_config_files="$ac_config_files src/Makefile www/Makefile src/version.h rem2html/Makefile rem2pdf/Makefile.PL rem2pdf/Makefile.top rem2pdf/bin/rem2pdf man/rem.1 man/rem2ps.1 man/remind.1 man/tkremind.1"
|
||||
ac_config_files="$ac_config_files src/Makefile www/Makefile src/version.h rem2html/Makefile rem2html/rem2html rem2pdf/Makefile.PL rem2pdf/Makefile.top rem2pdf/bin/rem2pdf man/rem.1 man/rem2ps.1 man/remind.1 man/tkremind.1"
|
||||
|
||||
cat >confcache <<\_ACEOF
|
||||
# This file is a shell script that caches the results of configure
|
||||
@@ -4910,6 +4922,7 @@ do
|
||||
"www/Makefile") CONFIG_FILES="$CONFIG_FILES www/Makefile" ;;
|
||||
"src/version.h") CONFIG_FILES="$CONFIG_FILES src/version.h" ;;
|
||||
"rem2html/Makefile") CONFIG_FILES="$CONFIG_FILES rem2html/Makefile" ;;
|
||||
"rem2html/rem2html") CONFIG_FILES="$CONFIG_FILES rem2html/rem2html" ;;
|
||||
"rem2pdf/Makefile.PL") CONFIG_FILES="$CONFIG_FILES rem2pdf/Makefile.PL" ;;
|
||||
"rem2pdf/Makefile.top") CONFIG_FILES="$CONFIG_FILES rem2pdf/Makefile.top" ;;
|
||||
"rem2pdf/bin/rem2pdf") CONFIG_FILES="$CONFIG_FILES rem2pdf/bin/rem2pdf" ;;
|
||||
|
||||
@@ -38,7 +38,7 @@ AC_CHECK_SIZEOF(unsigned long)
|
||||
AC_CHECK_SIZEOF(time_t)
|
||||
|
||||
dnl Checks for header files.
|
||||
AC_CHECK_HEADERS(sys/types.h glob.h wctype.h locale.h langinfo.h)
|
||||
AC_CHECK_HEADERS(sys/types.h glob.h wctype.h locale.h langinfo.h sys/inotify.h)
|
||||
|
||||
dnl Checks for typedefs, structures, and compiler characteristics.
|
||||
AC_STRUCT_TM
|
||||
@@ -86,13 +86,13 @@ if test "$?" != 0 ; then
|
||||
echo "*** COULD NOT DETERMINE RELEASE DATE: docs/WHATSNEW is incorrect!"
|
||||
exit 1
|
||||
fi
|
||||
AC_CHECK_FUNCS(setenv unsetenv glob mbstowcs setlocale initgroups)
|
||||
AC_CHECK_FUNCS(setenv unsetenv glob mbstowcs setlocale initgroups inotify_init1)
|
||||
|
||||
VERSION=04.02.09
|
||||
VERSION=04.03.00
|
||||
AC_SUBST(VERSION)
|
||||
AC_SUBST(PERL)
|
||||
AC_SUBST(PERLARTIFACTS)
|
||||
AC_SUBST(RELEASE_DATE)
|
||||
AC_CONFIG_FILES([src/Makefile www/Makefile src/version.h rem2html/Makefile rem2pdf/Makefile.PL rem2pdf/Makefile.top rem2pdf/bin/rem2pdf man/rem.1 man/rem2ps.1 man/remind.1 man/tkremind.1])
|
||||
AC_CONFIG_FILES([src/Makefile www/Makefile src/version.h rem2html/Makefile rem2html/rem2html rem2pdf/Makefile.PL rem2pdf/Makefile.top rem2pdf/bin/rem2pdf man/rem.1 man/rem2ps.1 man/remind.1 man/tkremind.1])
|
||||
AC_OUTPUT
|
||||
chmod a+x rem2pdf/bin/rem2pdf
|
||||
|
||||
@@ -1,5 +1,50 @@
|
||||
CHANGES TO REMIND
|
||||
|
||||
* VERSION 4.3 Patch 0 - 2024-02-29
|
||||
|
||||
- IMPROVEMENT: remind: If Remind is compiled on a system that supports
|
||||
inotify, then in server mode (-z0 or -zj) it monitors the reminders file
|
||||
and restarts itself if it detects a change, and also notifies the client.
|
||||
Moving inotify support directly into Remind means that tkremind no longer
|
||||
has to invoke a separate inotifywait process.
|
||||
|
||||
- IMPROVEMENT: remind: Set the CLOEXEC flag on files we open so we
|
||||
don't leak file descriptors to programs that we run. While I don't
|
||||
think there's a security issue here (any program you run can do
|
||||
anything as your userid anyway) it's best to be clean and tidy.
|
||||
|
||||
- IMPROVEMENT: remind: Add localization for the Catalan language, courtesy
|
||||
of Eloi Torrents.
|
||||
|
||||
- IMPROVEMENT: tkremind: Add a .desktop file and icon so TkRemind can be
|
||||
integrated into the desktop menu system, courtesy of Eloi Torrents.
|
||||
|
||||
- CHANGE: Add a new server mode with the "-zj" flag. This is just
|
||||
like "-z0" except it uses JSON messages to communicate with the
|
||||
client rather than an ad-hoc protocol. The "-z0" mode is still
|
||||
supported, but is deprecated.
|
||||
|
||||
- CHANGE: In server mode (-z0 or -zj) any RUN-type reminders, or message
|
||||
commands of the "-kcommand" type are run with standard input and standard
|
||||
output connected to /dev/null. NOTE INCOMPATIBILITY: If you previously
|
||||
relied on RUN-type reminders to pop up reminders in TkRemind, they no
|
||||
longer do. If you want this, you'll have to get the command that you
|
||||
run to pop up its own window with "xmessage" or something similar.
|
||||
|
||||
- IMPROVEMENT: tkremind: Make the "Go to date..." dialog non-modal.
|
||||
|
||||
- CHANGE: remind: Allow the argument to easterdate() and
|
||||
orthodoxeaster() to be omitted, in which case it defaults to
|
||||
today().
|
||||
|
||||
- BUG FIX: Miscellaneous man page fixes.
|
||||
|
||||
- BUG FIX: Fix a leap-year edge-case. The reminder: REM 29 MSG whatever
|
||||
was not triggered on Feb 29 of leap years.
|
||||
|
||||
- BUG FIX: rem2html: Make the version of rem2html track the version of
|
||||
Remind. Noted by Ian! D. Allen.
|
||||
|
||||
* VERSION 4.2 Patch 9 - 2024-02-04
|
||||
|
||||
- CHANGE: remind: Do not attempt to guess terminal background color on
|
||||
@@ -18,7 +63,7 @@ CHANGES TO REMIND
|
||||
|
||||
- MINOR NEW FEATURE: remind: The expression STRING * INT or INT * STRING
|
||||
is now accepted and yields a string that is INT concatenations of the
|
||||
origina STRING. In this case, INT must be non-negative and the total
|
||||
original STRING. In this case, INT must be non-negative and the total
|
||||
string length can't exceed $MaxStringLen.
|
||||
|
||||
- DOCUMENTATION: Add "Astronomical Algorithms" by Jean Meeus to bibliography.
|
||||
|
||||
53
include/lang/ca.rem
Normal file
53
include/lang/ca.rem
Normal file
@@ -0,0 +1,53 @@
|
||||
# Support for the Catalan language.
|
||||
# This file is part of REMIND.
|
||||
# REMIND is Copyright (C) 1992-2024 by Dianne Skoll
|
||||
# This file was created by Eloi Torrents <eloitor@disroot.org>
|
||||
|
||||
SET $Monday "dilluns"
|
||||
SET $Tuesday "dimarts"
|
||||
SET $Wednesday "dimecres"
|
||||
SET $Thursday "dijous"
|
||||
SET $Friday "divendres"
|
||||
SET $Saturday "dissabte"
|
||||
SET $Sunday "diumenge"
|
||||
|
||||
SET $January "gener"
|
||||
SET $February "febrer"
|
||||
SET $March "març"
|
||||
SET $April "abril"
|
||||
SET $May "maig"
|
||||
SET $June "juny"
|
||||
SET $July "juliol"
|
||||
SET $August "agost"
|
||||
SET $September "setembre"
|
||||
SET $October "octubre"
|
||||
SET $November "novembre"
|
||||
SET $December "desembre"
|
||||
|
||||
SET $Today "avui"
|
||||
SET $Tomorrow "demà"
|
||||
FSET subst_bx(a,d,t) iif(d==today()+2, "demà passat", "d'aquí " + (d-today()) + " dies")
|
||||
|
||||
# 1 d'abril vs 1 de maig.
|
||||
FSET subst_sx(a,d,t) iif(isany(substr(mon(d), 1, 1), "a", "o") , "d'", "de")
|
||||
FSET subst_ordinal(d) ""
|
||||
|
||||
BANNER Agenda pel %w, %d %s %m de %y%o:
|
||||
|
||||
SET $Am "am"
|
||||
SET $Pm "pm"
|
||||
|
||||
SET $Ago "fa"
|
||||
SET $Fromnow "des d'avui"
|
||||
SET $On "el dia"
|
||||
SET $Now "ara"
|
||||
SET $At "a les"
|
||||
|
||||
SET $Minute "minut"
|
||||
SET $Mplu "s"
|
||||
SET $Hour "hora"
|
||||
FSET subst_hours(h) iif(h==1, "1 hora", h + " hores")
|
||||
|
||||
SET $Is "és"
|
||||
SET $Was "va ser"
|
||||
SET $And "i"
|
||||
@@ -368,9 +368,12 @@ defaults to 1, and can range from 1 to 60. Note that the use of the
|
||||
\fB\-z\fR option also enables the \fB\-f\fR option.
|
||||
.PP
|
||||
.RS
|
||||
If you supply the option \fB\-z0\fR, \fBRemind\fR runs in a
|
||||
If you supply the option \fB\-zj\fR, \fBRemind\fR runs in a
|
||||
special mode called \fBserver mode\fR. This is documented
|
||||
in the tkremind man page; see tkremind(1).
|
||||
in the tkremind man page; see tkremind(1). The older server mode
|
||||
option \fB\-z0\fR still works, but is deprecated; it uses an ad-hoc
|
||||
method to communicate with the client rather than using JSON to communicate
|
||||
with the client.
|
||||
.RE
|
||||
.TP
|
||||
\fB\-u\fR\fIname\fR
|
||||
@@ -3080,11 +3083,12 @@ will produce undefined results.
|
||||
Returns the time of "civil twilight" on the specified \fIdate\fR. If
|
||||
\fIdate\fR is omitted, defaults to \fBtoday()\fR.
|
||||
.TP
|
||||
.B easterdate(dqi_arg)
|
||||
.B easterdate([dqi_arg])
|
||||
If \fIarg\fR is an \fBINT\fR, then returns the date of Easter Sunday
|
||||
for the specified year. If \fIarg\fR is a \fBDATE\fR or
|
||||
\fBDATETIME\fR, then returns the date of the next Easter Sunday on or
|
||||
after \fIarg\fR. (The time component of a datetime is ignored.)
|
||||
after \fIarg\fR. (The time component of a datetime is ignored.) If \fIarg\fR
|
||||
is omitted, then it defaults to \fBtoday()\fR.
|
||||
.RS
|
||||
.P
|
||||
Note that \fBeasterdate\fR computes the Western Easter. For the Orthodox
|
||||
@@ -3439,11 +3443,12 @@ the actual time, or a time supplied on the command line.
|
||||
Returns a string that is the ordinal number \fInum\fR. For example,
|
||||
\fBord(2)\fR returns "2nd", and \fBord(213)\fR returns "213th".
|
||||
.TP
|
||||
.B orthodoxeaster(dqi_arg)
|
||||
.B orthodoxeaster([dqi_arg])
|
||||
If \fIarg\fR is an \fBINT\fR, then returns the date of Orthodox Easter Sunday
|
||||
for the specified year. If \fIarg\fR is a \fBDATE\fR or
|
||||
\fBDATETIME\fR, then returns the date of the next Orthodox Easter Sunday on or
|
||||
after \fIarg\fR. (The time component of a datetime is ignored.)
|
||||
after \fIarg\fR. (The time component of a datetime is ignored.) If \fIarg\fR
|
||||
is omitted, then it defaults to \fBtoday()\fR.
|
||||
.RS
|
||||
.P
|
||||
Note that \fBorthodoxeaster\fR computes the Orthodox Easter. For the Western
|
||||
@@ -5255,7 +5260,7 @@ A number of system variables let you translate various phrases
|
||||
to other languages. These system variables are:
|
||||
.PP
|
||||
.TP
|
||||
.B $Monday, $Tuesday, $Wednesday, $Thursday, $Friday, $Saturday
|
||||
.B $Monday, $Tuesday, $Wednesday, $Thursday, $Friday, $Saturday, $Sunday
|
||||
Set each of these system variables to a string representing the corresponding
|
||||
day's name in your language. Strings must be valid UTF-8 strings.
|
||||
.TP
|
||||
|
||||
@@ -301,11 +301,11 @@ Today
|
||||
|
||||
.SH IMMEDIATE UPDATES
|
||||
|
||||
If you are running \fBTkRemind\fR on Linux and have the
|
||||
\fBinotifywait\fR program installed (part of the \fBinotify-tools\fR
|
||||
or similar package), then \fBTkRemind\fR redraws the calendar window
|
||||
\fIimmediately\fR if \fB$HOME/.reminders\fR changes (or, if it is a
|
||||
directory, any files in that directory change.)
|
||||
If you are running \fBTkRemind\fR on Linux and \fBRemind\fR has been
|
||||
compiled with \fBinotify\fR(7) support, then \fBTkRemind\fR redraws
|
||||
the calendar window \fIimmediately\fR if \fB$HOME/.reminders\fR
|
||||
changes (or, if it is a directory, any files in that directory
|
||||
change.)
|
||||
.PP
|
||||
This lets \fBTkRemind\fR react immediately to hand-edited reminders or
|
||||
to reminder files that are imported from another calendar system (for example,
|
||||
@@ -366,75 +366,91 @@ your hand-edited files in a separate \fB*.rem\fR file than \fBTkRemind\fR's
|
||||
|
||||
\fBRemind\fR has a special mode for interacting with programs like
|
||||
\fBTkRemind\fR. This mode is called \fIserver mode\fR and is
|
||||
selected by supplying the \fB\-z0\fR option to \fBRemind\fR.
|
||||
selected by supplying the \fB\-zj\fR option to \fBRemind\fR.
|
||||
|
||||
In server mode, \fBRemind\fR operates similar to daemon mode, except
|
||||
it reads commands (one per line)
|
||||
from standard input and writes status lines to standard output.
|
||||
it reads commands (one per line) from standard input and writes status
|
||||
lines to standard output. Each status line is a JSON object.
|
||||
|
||||
The commands accepted in server mode are:
|
||||
|
||||
.TP
|
||||
EXIT
|
||||
Terminate the \fBRemind\fR process. EOF on standard input does the
|
||||
same thing.
|
||||
same thing. \fBRemind\fR exits immediately without printing
|
||||
a JSON status line.
|
||||
|
||||
.TP
|
||||
STATUS
|
||||
Return the number of queued reminders.
|
||||
Return the number of queued reminders. The JSON object looks
|
||||
something like this:
|
||||
.nf
|
||||
|
||||
{"response":"queued","nqueued":n,"command":"STATUS"}
|
||||
|
||||
.fi
|
||||
where \fIn\fR is the number of reminders queued.
|
||||
|
||||
.TP
|
||||
QUEUE
|
||||
Returns the contents of the queue, printed between "NOTE queue" and
|
||||
"NOTE endqueue" lines.
|
||||
QUEUE or JSONQUEUE
|
||||
Returns the contents of the queue. The JSON object looks something
|
||||
like this:
|
||||
.nf
|
||||
|
||||
.TP
|
||||
JSONQUEUE
|
||||
Returns the contents of the queue in JSON format, printed between
|
||||
"NOTE JSONQUEUE" and "NOTE ENDJSONQUEUE" lines.
|
||||
{"response":"queue","queue":[ ... ],"command":"QUEUE"}
|
||||
|
||||
.fi
|
||||
The value of the \fBqueue\fR key is an array of JSON objects, each
|
||||
representing a queued reminder.
|
||||
|
||||
.TP
|
||||
REREAD
|
||||
Re-read the reminder file
|
||||
Re-read the reminder file. Returns the following status line:
|
||||
|
||||
.nf
|
||||
|
||||
{"response":"reread","command":"REREAD"}
|
||||
|
||||
.fi
|
||||
|
||||
.PP
|
||||
The status lines written are as follows:
|
||||
Additional status lines written are as follows:
|
||||
|
||||
.TP
|
||||
NOTE reminder \fItime\fR \fItag\fR
|
||||
Signifies the beginning of a timed reminder whose trigger time is
|
||||
\fItime\fR with tag \fItag\fR. If the reminder has no tag, an
|
||||
asterisk is supplied for \fItag\fR. All lines following this line
|
||||
are the body of the reminder, until the line \fBNOTE endreminder\fR
|
||||
is transmitted.
|
||||
.nf
|
||||
|
||||
{"response":"reminder","ttime":tt,"now":now,"tags":tags,"body":body}
|
||||
|
||||
.fi
|
||||
In this line, \fItt\fR is the trigger time of the reminder (expressed
|
||||
as a string), \fInow\fR is the current time, \fItags\fR (if present)
|
||||
is the tag or tags associated with the reminder, and \fIbody\fR is
|
||||
the body of the reminder. This response causes \fBTkRemind\fR to
|
||||
pop up a reminder notification.
|
||||
|
||||
.TP
|
||||
NOTE newdate
|
||||
.nf
|
||||
|
||||
{"response":"newdate"}
|
||||
|
||||
.fi
|
||||
|
||||
This line is emitted whenever \fBRemind\fR has detected a rollover of
|
||||
the system date. The front-end program should redraw its calendar
|
||||
or take whatever other action is needed.
|
||||
|
||||
.TP
|
||||
NOTE reread
|
||||
This line is emitted whenever the number of reminders in \fBRemind\fR's
|
||||
queue changes because of a date rollover or a \fBREREAD\fR command.
|
||||
The front-end should issue a \fBSTATUS\fR command in response to this
|
||||
message.
|
||||
.nf
|
||||
|
||||
.TP
|
||||
NOTE queued \fIn\fR
|
||||
This line is emitted in response to a \fBSTATUS\fR command. The number
|
||||
\fIn\fR is the number of reminders in the queue.
|
||||
{"response":"reread","command":"inotify"}
|
||||
|
||||
.TP
|
||||
NOTE queue
|
||||
Indicates that queue contents are about to follow. The end of the
|
||||
queue is indicated by a NOTE endqueue line.
|
||||
.fi
|
||||
|
||||
.TP
|
||||
NOTE JSONQUEUE
|
||||
Indicates that queue contents in JSON format are about to follow. The
|
||||
end of the queue is indicated by a NOTE ENDJSONQUEUE line.
|
||||
If \fBRemind\fR was compiled with support for \fBinotify\fR(7), then
|
||||
if it detects a change to the top-level reminder file or directory,
|
||||
it issues the above response. The front-end should redraw its
|
||||
calendar since this response indicates that a change has been made
|
||||
to the reminder file or directory.
|
||||
|
||||
.PP
|
||||
Please note that \fBRemind\fR can write a status message \fIat any time\fR
|
||||
|
||||
@@ -10,7 +10,7 @@ use Encode;
|
||||
|
||||
my %Options;
|
||||
|
||||
my $rem2html_version = '2.1';
|
||||
my $rem2html_version = '@VERSION@';
|
||||
|
||||
my($days, $shades, $moons, $classes, $Month, $Year, $Numdays, $Firstwkday, $Mondayfirst, $weeks,
|
||||
@Daynames, $Nextmon, $Nextlen, $Prevmon, $Prevlen);
|
||||
15
resources/tkremind.desktop
Executable file
15
resources/tkremind.desktop
Executable file
@@ -0,0 +1,15 @@
|
||||
[Desktop Entry]
|
||||
Type=Application
|
||||
Exec=tkremind
|
||||
StartupNotify=true
|
||||
Icon=tkremind
|
||||
Terminal=false
|
||||
Name=tkremind
|
||||
Comment=TkRemind Calendar Program
|
||||
Categories=Office;Calendar;
|
||||
Keywords=Calendar;remind;
|
||||
Keywords[ca]=Calendari;remind;
|
||||
Keywords[de]=Kalender;remind;
|
||||
Keywords[en_GB]=Calendar;remind;
|
||||
Keywords[es]=Calendario;remind;
|
||||
|
||||
BIN
resources/tkremind.png
Normal file
BIN
resources/tkremind.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.1 KiB |
214
scripts/tkremind
214
scripts/tkremind
@@ -110,6 +110,19 @@ if {$tcl_platform(platform) == "windows"} {
|
||||
exit 1
|
||||
}
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
# GetRemindVersion
|
||||
# Arguments:
|
||||
# none
|
||||
# Returns:
|
||||
# The version of Remind
|
||||
#---------------------------------------------------------------------------
|
||||
proc GetRemindVersion {} {
|
||||
global Remind
|
||||
set ver [exec sh -c "(echo \"banner %\"; echo \"msg \[version()\]%\") | $Remind -"]
|
||||
return $ver
|
||||
}
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
# GLOBAL VARIABLES
|
||||
#---------------------------------------------------------------------------
|
||||
@@ -203,9 +216,6 @@ set ConfigFile ""
|
||||
|
||||
set EditorPid -1
|
||||
|
||||
# Inotify file
|
||||
set InotifyFP ""
|
||||
|
||||
# Errors from last remind run
|
||||
set RemindErrors ""
|
||||
|
||||
@@ -294,6 +304,14 @@ set WarningHeaders [list "# Lines starting with REM TAG TKTAGnnn ... were create
|
||||
# Highest tag seen so far. Array of tags is stored in ReminderTags()
|
||||
set HighestTagSoFar 0
|
||||
|
||||
# Check Remind version
|
||||
set ver [GetRemindVersion]
|
||||
|
||||
if {"$ver" < "04.03.00"} {
|
||||
tk_dialog .error Error "This version of TkRemind requires Remind version 04.03.00 or newer; you have version $ver" error 0 OK
|
||||
exit 1
|
||||
}
|
||||
|
||||
proc get_weekday { yyyymmdd } {
|
||||
global EnglishDayNames
|
||||
return [lindex $EnglishDayNames [clock format [clock scan $yyyymmdd] -format %w -locale C]]
|
||||
@@ -1528,9 +1546,7 @@ proc GotoDialog {} {
|
||||
bind .g <KeyPress-Escape> ".g.b.cancel flash; .g.b.cancel invoke"
|
||||
CenterWindow .g .
|
||||
set oldFocus [focus]
|
||||
grab .g
|
||||
focus .g.y.e
|
||||
tkwait window .g
|
||||
catch {focus $oldFocus}
|
||||
}
|
||||
|
||||
@@ -1551,7 +1567,7 @@ proc DoGoto {} {
|
||||
set month [lsearch -exact $MonthNames [.g.mon cget -text]]
|
||||
set CurMonth $month
|
||||
set CurYear $year
|
||||
destroy .g
|
||||
catch { destroy .g }
|
||||
FillCalWindow
|
||||
}
|
||||
|
||||
@@ -1560,19 +1576,14 @@ proc DoGoto {} {
|
||||
#---------------------------------------------------------------------------
|
||||
proc Quit {} {
|
||||
global Option
|
||||
global InotifyFP
|
||||
if { !$Option(ConfirmQuit) } {
|
||||
destroy .
|
||||
StopBackgroundRemindDaemon
|
||||
catch { exec kill [pid $InotifyFP] }
|
||||
catch { close $InotifyFP }
|
||||
exit 0
|
||||
}
|
||||
if { [tk_dialog .question "Confirm..." {Really quit?} question 0 No Yes] } {
|
||||
destroy .
|
||||
StopBackgroundRemindDaemon
|
||||
catch { exec kill [pid $InotifyFP] }
|
||||
catch { close $InotifyFP }
|
||||
exit 0
|
||||
}
|
||||
}
|
||||
@@ -2603,7 +2614,6 @@ proc BrowseForFileRead {w {dir ""}} {
|
||||
cd $cwd
|
||||
$w.entry delete 0 end
|
||||
}
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
# StartBackgroundRemindDaemon
|
||||
# Arguments:
|
||||
@@ -2616,9 +2626,9 @@ proc BrowseForFileRead {w {dir ""}} {
|
||||
proc StartBackgroundRemindDaemon {} {
|
||||
global Remind DaemonFile ReminderFile Option TwentyFourHourMode
|
||||
if {$TwentyFourHourMode} {
|
||||
set problem [catch { set DaemonFile [open "|$Remind -b1 -z0 -itkremind=1 $Option(ExtraRemindArgs) $ReminderFile" "r+"] } err]
|
||||
set problem [catch { set DaemonFile [open "|$Remind -b1 -zj -itkremind=1 $Option(ExtraRemindArgs) $ReminderFile" "r+"] } err]
|
||||
} else {
|
||||
set problem [catch { set DaemonFile [open "|$Remind -z0 -itkremind=1 $Option(ExtraRemindArgs) $ReminderFile" "r+"] } err]
|
||||
set problem [catch { set DaemonFile [open "|$Remind -zj -itkremind=1 $Option(ExtraRemindArgs) $ReminderFile" "r+"] } err]
|
||||
}
|
||||
if {$problem} {
|
||||
tk_dialog .error Error "Can't start Remind daemon in background: $err" error 0 OK
|
||||
@@ -2674,19 +2684,19 @@ proc RestartBackgroundRemindDaemon {} {
|
||||
#---------------------------------------------------------------------------
|
||||
# ShowQueue
|
||||
# Arguments:
|
||||
# file -- file channel that is readable
|
||||
# queue - the queue
|
||||
# Returns:
|
||||
# nothing
|
||||
# Description:
|
||||
# Dumps the debugging queue listing
|
||||
#---------------------------------------------------------------------------
|
||||
proc ShowQueue { file } {
|
||||
proc ShowQueue { queue } {
|
||||
set w .queuedbg
|
||||
catch { destroy $w }
|
||||
toplevel $w
|
||||
wm title $w "Queue (Debugging Output)"
|
||||
wm iconname $w "Queue Dbg"
|
||||
text $w.t -width 80 -height 30 -wrap word -yscrollcommand "$w.sb set"
|
||||
text $w.t -fg black -bg white -width 80 -height 30 -wrap word -yscrollcommand "$w.sb set"
|
||||
scrollbar $w.sb -orient vertical -command "$w.text yview"
|
||||
button $w.ok -text "OK" -command "destroy $w"
|
||||
grid $w.t -row 0 -column 0 -sticky nsew
|
||||
@@ -2697,31 +2707,34 @@ proc ShowQueue { file } {
|
||||
grid rowconfigure $w 0 -weight 1
|
||||
grid rowconfigure $w 1 -weight 0
|
||||
CenterWindow $w .
|
||||
while (1) {
|
||||
# We should only get one line
|
||||
gets $file line
|
||||
if {$line == "NOTE ENDJSONQUEUE"} {
|
||||
break
|
||||
}
|
||||
if {[catch {set obj [::json::json2dict $line]}]} {
|
||||
continue;
|
||||
}
|
||||
set obj [lsort -command sort_q $obj]
|
||||
set did 0
|
||||
foreach q $obj {
|
||||
$w.t insert end "$q\n"
|
||||
set did 1
|
||||
}
|
||||
if { $did == 0 } {
|
||||
$w.t insert end "(Queue is empty)\n"
|
||||
set obj [lsort -command sort_q $queue]
|
||||
set did 0
|
||||
$w.t tag configure grey -background "#DDDDDD" -selectbackground "#999999"
|
||||
set toggle 0
|
||||
foreach q $obj {
|
||||
if { $did > 0 } {
|
||||
$w.t insert end "\n"
|
||||
}
|
||||
foreach r $q {
|
||||
if { $toggle != 0 } {
|
||||
$w.t insert end "$r " grey
|
||||
} else {
|
||||
$w.t insert end "$r "
|
||||
}
|
||||
}
|
||||
$w.t insert end "\n"
|
||||
set toggle [expr 1 - $toggle]
|
||||
set did 1
|
||||
}
|
||||
if { $did == 0 } {
|
||||
$w.t insert end "(Queue is empty)\n"
|
||||
}
|
||||
$w.t configure -state disabled
|
||||
}
|
||||
|
||||
proc sort_q { a b } {
|
||||
set a_ttime [dict get $a nextttime]
|
||||
set b_ttime [dict get $b nextttime]
|
||||
set a_ttime [dict get $a nexttime]
|
||||
set b_ttime [dict get $b nexttime]
|
||||
if {$a_ttime < $b_ttime} {
|
||||
return -1
|
||||
}
|
||||
@@ -2748,43 +2761,63 @@ proc DaemonReadable { file } {
|
||||
catch { close $file }
|
||||
return
|
||||
}
|
||||
switch -glob -- $line {
|
||||
"NOTE reminder*" {
|
||||
scan $line "NOTE reminder %s %s %s" time now tag
|
||||
IssueBackgroundReminder $file $time $now $tag
|
||||
}
|
||||
"NOTE JSONQUEUE" {
|
||||
ShowQueue $file
|
||||
}
|
||||
"NOTE newdate" {
|
||||
# Date has rolled over -- clear "ignore" list
|
||||
catch { unset Ignore}
|
||||
Initialize
|
||||
FillCalWindow
|
||||
ShowTodaysReminders
|
||||
}
|
||||
"NOTE reread" {
|
||||
puts $file "STATUS"
|
||||
flush $file
|
||||
}
|
||||
"NOTE queued*" {
|
||||
scan $line "NOTE queued %d" n
|
||||
if {[catch {set obj [::json::json2dict $line]}]} {
|
||||
return;
|
||||
}
|
||||
if (![dict exists $obj response]) {
|
||||
return;
|
||||
}
|
||||
set response [dict get $obj response]
|
||||
switch -- $response {
|
||||
"queued" {
|
||||
set n [dict get $obj nqueued]
|
||||
if {$n == 1} {
|
||||
.b.nqueued configure -text "1 reminder queued"
|
||||
} else {
|
||||
.b.nqueued configure -text "$n reminders queued"
|
||||
}
|
||||
}
|
||||
default {
|
||||
puts stderr "Unknown message from daemon: $line\n"
|
||||
}
|
||||
}
|
||||
"reminder" {
|
||||
set time [dict get $obj ttime]
|
||||
set now [dict get $obj now]
|
||||
set tag "*"
|
||||
if {[dict exists $obj tags]} {
|
||||
set tag [dict get $obj tags]
|
||||
}
|
||||
set body [dict get $obj body]
|
||||
IssueBackgroundReminder $body $time $now $tag
|
||||
}
|
||||
"queue" {
|
||||
set queue [dict get $obj queue]
|
||||
ShowQueue $queue
|
||||
}
|
||||
"newdate" {
|
||||
# Date has rolled over -- clear "ignore" list
|
||||
catch { unset Ignore}
|
||||
Initialize
|
||||
FillCalWindow
|
||||
ShowTodaysReminders
|
||||
}
|
||||
"reread" {
|
||||
if {[dict exists $obj command]} {
|
||||
set cmd [dict get $obj command]
|
||||
if {"$cmd" == "inotify"} {
|
||||
FillCalWindow
|
||||
}
|
||||
}
|
||||
puts $file "STATUS"
|
||||
flush $file
|
||||
}
|
||||
default {
|
||||
puts stderr "Unknown message from daemon: $line\n"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
# IssueBackgroundReminder
|
||||
# Arguments:
|
||||
# file -- file channel that is readable
|
||||
# body -- body of reminder
|
||||
# time -- time of reminder
|
||||
# now -- current time according to Remind daemon
|
||||
# tag -- tag for reminder, or "*" if no tag
|
||||
@@ -2793,26 +2826,14 @@ proc DaemonReadable { file } {
|
||||
# Description:
|
||||
# Reads a background reminder from daemon and pops up window.
|
||||
#---------------------------------------------------------------------------
|
||||
proc IssueBackgroundReminder { file time now tag } {
|
||||
proc IssueBackgroundReminder { body time now tag } {
|
||||
global BgCounter Option Ignore
|
||||
if {$Option(Deiconify)} {
|
||||
wm deiconify .
|
||||
}
|
||||
|
||||
set msg ""
|
||||
set line ""
|
||||
while (1) {
|
||||
gets $file line
|
||||
if {$line == "NOTE endreminder"} {
|
||||
break
|
||||
}
|
||||
if {$msg != ""} {
|
||||
append msg "\n";
|
||||
}
|
||||
append msg $line
|
||||
}
|
||||
# Do nothing if it's blank -- was probably a RUN-type reminder.
|
||||
if {$msg == ""} {
|
||||
if {$body == ""} {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -2827,17 +2848,17 @@ proc IssueBackgroundReminder { file time now tag } {
|
||||
wm iconname $w "Reminder"
|
||||
wm title $w "Timed reminder ($time)"
|
||||
label $w.l -text "Reminder for $time issued at $now"
|
||||
message $w.msg -width 6i -text $msg
|
||||
message $w.msg -width 6i -text $body
|
||||
frame $w.b
|
||||
|
||||
# Automatically shut down window after a minute if option says so
|
||||
set after_token [after 60000 [list ClosePopup $w "" $Option(MailAddr) $Option(AutoClose) "" $tag $msg $time]]
|
||||
set after_token [after 60000 [list ClosePopup $w "" $Option(MailAddr) $Option(AutoClose) "" $tag $body $time]]
|
||||
|
||||
wm protocol $w WM_DELETE_WINDOW [list ClosePopup $w $after_token "" 1 "" $tag $msg $time]
|
||||
button $w.ok -text "OK" -command [list ClosePopup $w $after_token "" 1 "" $tag $msg $time]
|
||||
wm protocol $w WM_DELETE_WINDOW [list ClosePopup $w $after_token "" 1 "" $tag $body $time]
|
||||
button $w.ok -text "OK" -command [list ClosePopup $w $after_token "" 1 "" $tag $body $time]
|
||||
if {$tag != "*"} {
|
||||
button $w.nomore -text "Don't remind me again today" -command [list ClosePopup $w $after_token "" 1 "ignore" $tag $msg $time]
|
||||
button $w.kill -text "Delete this reminder completely" -command [list ClosePopup $w $after_token "" 1 "kill" $tag $msg $time]
|
||||
button $w.nomore -text "Don't remind me again today" -command [list ClosePopup $w $after_token "" 1 "ignore" $tag $body $time]
|
||||
button $w.kill -text "Delete this reminder completely" -command [list ClosePopup $w $after_token "" 1 "kill" $tag $body $time]
|
||||
}
|
||||
pack $w.l -side top
|
||||
pack $w.msg -side top -expand 1 -fill both
|
||||
@@ -2855,7 +2876,7 @@ proc IssueBackgroundReminder { file time now tag } {
|
||||
}
|
||||
if {$Option(RunCmd) != ""} {
|
||||
if {$Option(FeedReminder)} {
|
||||
FeedReminderToCommand $Option(RunCmd) "$time: $msg"
|
||||
FeedReminderToCommand $Option(RunCmd) "$time: $body"
|
||||
} else {
|
||||
exec "/bin/sh" "-c" $Option(RunCmd) "&"
|
||||
}
|
||||
@@ -2905,7 +2926,7 @@ proc main {} {
|
||||
|
||||
global AppendFile HighestTagSoFar DayNames
|
||||
catch {
|
||||
puts "\nTkRemind Copyright (C) 1996-2021 Dianne Skoll"
|
||||
puts "\nTkRemind Copyright (C) 1996-2024 Dianne Skoll"
|
||||
}
|
||||
catch { SetFonts }
|
||||
Initialize
|
||||
@@ -2924,7 +2945,6 @@ proc main {} {
|
||||
CreateCalWindow $DayNames
|
||||
FillCalWindow
|
||||
StartBackgroundRemindDaemon
|
||||
SetupInotify
|
||||
DisplayTimeContinuously
|
||||
}
|
||||
|
||||
@@ -3956,30 +3976,6 @@ proc SetFonts {} {
|
||||
set SetFontsWorked 1
|
||||
}
|
||||
|
||||
# Set up inotify to watch for changes to reminder file/directory
|
||||
proc SetupInotify {} {
|
||||
global InotifyFP
|
||||
global ReminderFile
|
||||
set failed [catch {set InotifyFP [open "|inotifywait -r -q -m -e close_write -e move -e create -e delete $ReminderFile < /dev/null 2>/dev/null" "r"] } ]
|
||||
if {$failed} {
|
||||
# inotifywait probably not available... meh.
|
||||
return
|
||||
}
|
||||
fileevent $InotifyFP readable [list InotifyReadable $InotifyFP]
|
||||
}
|
||||
|
||||
# Called when inotifywait reports an event. Schedule a calendar update
|
||||
# and daemon reload.
|
||||
proc InotifyReadable { fp } {
|
||||
catch { set num [gets $fp line] }
|
||||
if {$num < 0} {
|
||||
catch { exec kill [pid $fp] }
|
||||
catch { close $fp }
|
||||
return
|
||||
}
|
||||
ScheduleUpdateForChanges
|
||||
}
|
||||
|
||||
### Balloon help
|
||||
set Balloon(HelpTime) 400
|
||||
set Balloon(StayTime) 3500
|
||||
|
||||
@@ -63,6 +63,15 @@ install: all
|
||||
done
|
||||
-mkdir -p $(DESTDIR)$(datarootdir)/remind || true
|
||||
cp -R ../include/* $(DESTDIR)$(datarootdir)/remind
|
||||
-mkdir -p $(DESTDIR)$(prefix)/icons
|
||||
-mkdir -p $(DESTDIR)$(prefix)/applications
|
||||
$(INSTALL_DATA) $(srcdir)/../resources/tkremind.png $(DESTDIR)$(prefix)/icons
|
||||
$(INSTALL_PROGRAM) $(srcdir)/../resources/tkremind.desktop $(DESTDIR)$(prefix)/applications
|
||||
-if test "$(DESTDIR)" = ""; then \
|
||||
update-desktop-database < /dev/null > /dev/null 2>&1 ; \
|
||||
xdg-icon-resource install --novendor --size 64 $(DESTDIR)$(prefix)/icons/tkremind.png < /dev/null > /dev/null 2>&1; \
|
||||
xdg-desktop-menu install --novendor $(DESTDIR)$(prefix)/applications/tkremind.desktop < /dev/null > /dev/null 2>&1 ; \
|
||||
fi
|
||||
|
||||
install-stripped: install
|
||||
strip $(DESTDIR)$(bindir)/remind || true
|
||||
|
||||
@@ -475,7 +475,7 @@ void PrintJSONKeyPairTime(char const *name, int t)
|
||||
}
|
||||
|
||||
#ifdef REM_USE_WCHAR
|
||||
void PutWideChar(wchar_t const wc)
|
||||
void PutWideChar(wchar_t const wc, DynamicBuffer *output)
|
||||
{
|
||||
char buf[MB_CUR_MAX+1];
|
||||
int len;
|
||||
@@ -483,7 +483,11 @@ void PutWideChar(wchar_t const wc)
|
||||
len = wctomb(buf, wc);
|
||||
if (len > 0) {
|
||||
buf[len] = 0;
|
||||
fputs(buf, stdout);
|
||||
if (output) {
|
||||
DBufPuts(output, buf);
|
||||
} else {
|
||||
fputs(buf, stdout);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -1227,7 +1231,7 @@ static void PrintLeft(char const *s, int width, char pad)
|
||||
ws = buf;
|
||||
for (i=0; i<width;) {
|
||||
if (*ws) {
|
||||
PutWideChar(*ws++);
|
||||
PutWideChar(*ws++, NULL);
|
||||
i+= wcwidth(*ws);
|
||||
} else {
|
||||
break;
|
||||
@@ -1235,7 +1239,7 @@ static void PrintLeft(char const *s, int width, char pad)
|
||||
}
|
||||
/* Mop up any potential combining characters */
|
||||
while (*ws && wcwidth(*ws) == 0) {
|
||||
PutWideChar(*ws++);
|
||||
PutWideChar(*ws++, NULL);
|
||||
}
|
||||
|
||||
/* Possibly send lrm control sequence */
|
||||
@@ -1308,7 +1312,7 @@ static void PrintCentered(char const *s, int width, char *pad)
|
||||
for (i=0; i<d; i++) fputs(pad, stdout);
|
||||
for (i=0; i<width; i++) {
|
||||
if (*ws) {
|
||||
PutWideChar(*ws++);
|
||||
PutWideChar(*ws++, NULL);
|
||||
if (wcwidth(*ws) == 0) {
|
||||
/* Don't count this character... it's zero-width */
|
||||
i--;
|
||||
@@ -1319,7 +1323,7 @@ static void PrintCentered(char const *s, int width, char *pad)
|
||||
}
|
||||
/* Mop up any potential combining characters */
|
||||
while (*ws && wcwidth(*ws) == 0) {
|
||||
PutWideChar(*ws++);
|
||||
PutWideChar(*ws++, NULL);
|
||||
}
|
||||
/* Possibly send lrm control sequence */
|
||||
send_lrm();
|
||||
@@ -1448,7 +1452,7 @@ static int WriteOneColLine(int col)
|
||||
}
|
||||
numwritten += wcwidth(*ws);
|
||||
}
|
||||
PutWideChar(*ws);
|
||||
PutWideChar(*ws, NULL);
|
||||
}
|
||||
}
|
||||
e->wc_pos = ws;
|
||||
@@ -1463,7 +1467,7 @@ static int WriteOneColLine(int col)
|
||||
if (wcwidth(*ws) > 0) {
|
||||
numwritten += wcwidth(*ws);
|
||||
}
|
||||
PutWideChar(*ws);
|
||||
PutWideChar(*ws, NULL);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2306,15 +2310,9 @@ void WriteJSONTrigger(Trigger const *t, int include_tags, int today)
|
||||
if (t->noqueue) {
|
||||
PrintJSONKeyPairInt("noqueue", 1);
|
||||
}
|
||||
if (*t->sched) {
|
||||
PrintJSONKeyPairString("sched", t->sched);
|
||||
}
|
||||
if (*t->warn) {
|
||||
PrintJSONKeyPairString("warn", t->warn);
|
||||
}
|
||||
if (*t->omitfunc) {
|
||||
PrintJSONKeyPairString("omitfunc", t->omitfunc);
|
||||
}
|
||||
PrintJSONKeyPairString("sched", t->sched);
|
||||
PrintJSONKeyPairString("warn", t->warn);
|
||||
PrintJSONKeyPairString("omitfunc", t->omitfunc);
|
||||
if (t->addomit) {
|
||||
PrintJSONKeyPairInt("addomit", 1);
|
||||
}
|
||||
|
||||
@@ -10,6 +10,9 @@
|
||||
/* Define if you have the <sys/types.h> header file. */
|
||||
#undef HAVE_SYS_TYPES_H
|
||||
|
||||
/* Define if you have the <sys/inotify.h> header file. */
|
||||
#undef HAVE_SYS_INOTIFY_H
|
||||
|
||||
/* Define if you have the <glob.h> header file */
|
||||
#undef HAVE_GLOB_H
|
||||
|
||||
@@ -17,6 +20,8 @@
|
||||
|
||||
#undef HAVE_LOCALE_H
|
||||
|
||||
#undef HAVE_INOTIFY_INIT1
|
||||
|
||||
#undef HAVE_LANGINFO_H
|
||||
|
||||
#undef HAVE_GLOB
|
||||
|
||||
39
src/dorem.c
39
src/dorem.c
@@ -192,7 +192,7 @@ int DoRem(ParsePtr p)
|
||||
|
||||
r = OK;
|
||||
if (ShouldTriggerReminder(&trig, &tim, dse, &err)) {
|
||||
if ( (r=TriggerReminder(p, &trig, &tim, dse, 0)) ) {
|
||||
if ( (r=TriggerReminder(p, &trig, &tim, dse, 0, NULL)) ) {
|
||||
FreeTrig(&trig);
|
||||
return r;
|
||||
}
|
||||
@@ -910,7 +910,7 @@ static int ParseScanFrom(ParsePtr s, Trigger *t, int type)
|
||||
/* Trigger the reminder if it's a RUN or MSG type. */
|
||||
/* */
|
||||
/***************************************************************/
|
||||
int TriggerReminder(ParsePtr p, Trigger *t, TimeTrig *tim, int dse, int is_queued)
|
||||
int TriggerReminder(ParsePtr p, Trigger *t, TimeTrig *tim, int dse, int is_queued, DynamicBuffer *output)
|
||||
{
|
||||
int r, y, m, d;
|
||||
char PrioExpr[VAR_NAME_LEN+25];
|
||||
@@ -977,7 +977,7 @@ int TriggerReminder(ParsePtr p, Trigger *t, TimeTrig *tim, int dse, int is_queue
|
||||
}
|
||||
/* If it's a MSG-type reminder, and no -k option was used, issue the banner. */
|
||||
if ((t->typ == MSG_TYPE || t->typ == MSF_TYPE)
|
||||
&& !DidMsgReminder && !NextMode && !msg_command) {
|
||||
&& !DidMsgReminder && !NextMode && !msg_command && !is_queued) {
|
||||
DidMsgReminder = 1;
|
||||
if (!DoSubstFromString(DBufValue(&Banner), &buf,
|
||||
DSEToday, NO_TIME) &&
|
||||
@@ -1044,11 +1044,18 @@ int TriggerReminder(ParsePtr p, Trigger *t, TimeTrig *tim, int dse, int is_queue
|
||||
return E_NO_MEM;
|
||||
}
|
||||
|
||||
printf("%s%s%s\n", DBufValue(&calRow), DBufValue(&pre_buf), DBufValue(&buf));
|
||||
r = OK;
|
||||
if (output) {
|
||||
if (DBufPuts(output, DBufValue(&calRow)) != OK) r = E_NO_MEM;
|
||||
if (DBufPuts(output, DBufValue(&pre_buf)) != OK) r = E_NO_MEM;
|
||||
if (DBufPuts(output, DBufValue(&buf)) != OK) r = E_NO_MEM;
|
||||
} else {
|
||||
printf("%s%s%s\n", DBufValue(&calRow), DBufValue(&pre_buf), DBufValue(&buf));
|
||||
}
|
||||
DBufFree(&buf);
|
||||
DBufFree(&pre_buf);
|
||||
DBufFree(&calRow);
|
||||
return OK;
|
||||
return r;
|
||||
}
|
||||
|
||||
/* Correct colors */
|
||||
@@ -1144,23 +1151,27 @@ int TriggerReminder(ParsePtr p, Trigger *t, TimeTrig *tim, int dse, int is_queue
|
||||
case MSG_TYPE:
|
||||
case PASSTHRU_TYPE:
|
||||
if (msg_command) {
|
||||
DoMsgCommand(msg_command, DBufValue(&buf));
|
||||
DoMsgCommand(msg_command, DBufValue(&buf), is_queued);
|
||||
} else {
|
||||
/* Add a space before "NOTE endreminder" */
|
||||
if (IsServerMode() && !strncmp(DBufValue(&buf), "NOTE endreminder", 16)) {
|
||||
printf(" %s", DBufValue(&buf));
|
||||
if (output) {
|
||||
DBufPuts(output, DBufValue(&buf));
|
||||
} else {
|
||||
printf("%s", DBufValue(&buf));
|
||||
/* Add a space before "NOTE endreminder" */
|
||||
if (IsServerMode() && !strncmp(DBufValue(&buf), "NOTE endreminder", 16)) {
|
||||
printf(" %s", DBufValue(&buf));
|
||||
} else {
|
||||
printf("%s", DBufValue(&buf));
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case MSF_TYPE:
|
||||
FillParagraph(DBufValue(&buf));
|
||||
FillParagraph(DBufValue(&buf), output);
|
||||
break;
|
||||
|
||||
case RUN_TYPE:
|
||||
System(DBufValue(&buf));
|
||||
System(DBufValue(&buf), is_queued);
|
||||
break;
|
||||
|
||||
default: /* Unknown/illegal type? */
|
||||
@@ -1387,7 +1398,7 @@ static int ParsePriority(ParsePtr s, Trigger *t)
|
||||
/* Execute the '-k' command, escaping shell chars in message. */
|
||||
/* */
|
||||
/***************************************************************/
|
||||
int DoMsgCommand(char const *cmd, char const *msg)
|
||||
int DoMsgCommand(char const *cmd, char const *msg, int is_queued)
|
||||
{
|
||||
int r;
|
||||
int i, l;
|
||||
@@ -1424,7 +1435,7 @@ int DoMsgCommand(char const *cmd, char const *msg)
|
||||
}
|
||||
r = OK;
|
||||
|
||||
System(DBufValue(&execBuffer));
|
||||
System(DBufValue(&execBuffer), is_queued);
|
||||
|
||||
finished:
|
||||
DBufFree(&buf);
|
||||
|
||||
18
src/files.c
18
src/files.c
@@ -15,7 +15,7 @@
|
||||
#include "config.h"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <ctype.h>
|
||||
@@ -100,6 +100,18 @@ static int CheckSafety (void);
|
||||
static int CheckSafetyAux (struct stat *statbuf);
|
||||
static int PopFile (void);
|
||||
static int IncludeCmd(char const *);
|
||||
|
||||
void set_cloexec(int fd)
|
||||
{
|
||||
int flags;
|
||||
flags = fcntl(fd, F_GETFD);
|
||||
if (flags >= 0) {
|
||||
flags |= FD_CLOEXEC;
|
||||
fcntl(fd, F_SETFD, flags);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void OpenPurgeFile(char const *fname, char const *mode)
|
||||
{
|
||||
DynamicBuffer fname_buf;
|
||||
@@ -123,6 +135,7 @@ static void OpenPurgeFile(char const *fname, char const *mode)
|
||||
if (!PurgeFP) {
|
||||
fprintf(ErrFp, "Cannot open `%s' for writing: %s\n", DBufValue(&fname_buf), strerror(errno));
|
||||
}
|
||||
set_cloexec(fileno(PurgeFP));
|
||||
DBufFree(&fname_buf);
|
||||
}
|
||||
|
||||
@@ -327,6 +340,7 @@ int OpenFile(char const *fname)
|
||||
}
|
||||
} else {
|
||||
fp = fopen(fname, "r");
|
||||
if (fp) set_cloexec(fileno(fp));
|
||||
if (DebugFlag & DB_TRACE_FILES) {
|
||||
fprintf(ErrFp, "Reading `%s': Opening file on disk\n", fname);
|
||||
}
|
||||
@@ -346,6 +360,7 @@ int OpenFile(char const *fname)
|
||||
if (strcmp(fname, "-")) {
|
||||
fp = fopen(fname, "r");
|
||||
if (!fp || !CheckSafety()) return E_CANT_OPEN;
|
||||
set_cloexec(fileno(fp));
|
||||
if (PurgeMode) OpenPurgeFile(fname, "w");
|
||||
} else {
|
||||
fp = stdin;
|
||||
@@ -542,6 +557,7 @@ static int PopFile(void)
|
||||
if (strcmp(i->filename, "-")) {
|
||||
fp = fopen(i->filename, "r");
|
||||
if (!fp || !CheckSafety()) return E_CANT_OPEN;
|
||||
set_cloexec(fileno(fp));
|
||||
if (PurgeMode) OpenPurgeFile(i->filename, "a");
|
||||
} else {
|
||||
fp = stdin;
|
||||
|
||||
40
src/funcs.c
40
src/funcs.c
@@ -251,7 +251,7 @@ BuiltinFunc Func[] = {
|
||||
{ "defined", 1, 1, 0, FDefined },
|
||||
{ "dosubst", 1, 3, 0, FDosubst },
|
||||
{ "dusk", 0, 1, 0, FDusk },
|
||||
{ "easterdate", 1, 1, 0, FEasterdate },
|
||||
{ "easterdate", 0, 1, 0, FEasterdate },
|
||||
{ "evaltrig", 1, 2, 0, FEvalTrig },
|
||||
{ "filedate", 1, 1, 0, FFiledate },
|
||||
{ "filedatetime", 1, 1, 0, FFiledatetime },
|
||||
@@ -289,7 +289,7 @@ BuiltinFunc Func[] = {
|
||||
{ "nonomitted", 2, NO_MAX, 0, FNonomitted },
|
||||
{ "now", 0, 0, 0, FNow },
|
||||
{ "ord", 1, 1, 1, FOrd },
|
||||
{ "orthodoxeaster",1, 1, 0, FOrthodoxeaster },
|
||||
{ "orthodoxeaster",0, 1, 0, FOrthodoxeaster },
|
||||
{ "ostype", 0, 0, 1, FOstype },
|
||||
{ "pad", 3, 4, 1, FPad },
|
||||
{ "plural", 1, 3, 1, FPlural },
|
||||
@@ -2364,13 +2364,17 @@ static int FEasterdate(func_info *info)
|
||||
{
|
||||
int y, m, d;
|
||||
int g, c, x, z, e, n;
|
||||
if (ARG(0).type == INT_TYPE) {
|
||||
y = ARGV(0);
|
||||
if (y < BASE) return E_2LOW;
|
||||
else if (y > BASE+YR_RANGE) return E_2HIGH;
|
||||
} else if (HASDATE(ARG(0))) {
|
||||
FromDSE(DATEPART(ARG(0)), &y, &m, &d); /* We just want the year */
|
||||
} else return E_BAD_TYPE;
|
||||
if (Nargs == 0) {
|
||||
FromDSE(DSEToday, &y, &m, &d);
|
||||
} else {
|
||||
if (ARG(0).type == INT_TYPE) {
|
||||
y = ARGV(0);
|
||||
if (y < BASE) return E_2LOW;
|
||||
else if (y > BASE+YR_RANGE) return E_2HIGH;
|
||||
} else if (HASDATE(ARG(0))) {
|
||||
FromDSE(DATEPART(ARG(0)), &y, &m, &d); /* We just want the year */
|
||||
} else return E_BAD_TYPE;
|
||||
}
|
||||
|
||||
do {
|
||||
g = (y % 19) + 1; /* golden number */
|
||||
@@ -2409,13 +2413,17 @@ static int FOrthodoxeaster(func_info *info)
|
||||
{
|
||||
int y, m, d;
|
||||
int a, b, c, dd, e, f, dse;
|
||||
if (ARG(0).type == INT_TYPE) {
|
||||
y = ARGV(0);
|
||||
if (y < BASE) return E_2LOW;
|
||||
else if (y > BASE+YR_RANGE) return E_2HIGH;
|
||||
} else if (HASDATE(ARG(0))) {
|
||||
FromDSE(DATEPART(ARG(0)), &y, &m, &d); /* We just want the year */
|
||||
} else return E_BAD_TYPE;
|
||||
if (Nargs == 0) {
|
||||
FromDSE(DSEToday, &y, &m, &d);
|
||||
} else {
|
||||
if (ARG(0).type == INT_TYPE) {
|
||||
y = ARGV(0);
|
||||
if (y < BASE) return E_2LOW;
|
||||
else if (y > BASE+YR_RANGE) return E_2HIGH;
|
||||
} else if (HASDATE(ARG(0))) {
|
||||
FromDSE(DATEPART(ARG(0)), &y, &m, &d); /* We just want the year */
|
||||
} else return E_BAD_TYPE;
|
||||
}
|
||||
|
||||
do {
|
||||
a = y % 4;
|
||||
|
||||
@@ -92,6 +92,7 @@ EXTERN INIT( int DontQueue, 0);
|
||||
EXTERN INIT( int NumQueued, 0);
|
||||
EXTERN INIT( int DontIssueAts, 0);
|
||||
EXTERN INIT( int Daemon, 0);
|
||||
EXTERN INIT( int DaemonJSON, 0);
|
||||
EXTERN INIT( char DateSep, DATESEP);
|
||||
EXTERN INIT( char TimeSep, TIMESEP);
|
||||
EXTERN INIT( char DateTimeSep, DATETIMESEP);
|
||||
|
||||
@@ -425,7 +425,11 @@ void InitRemind(int argc, char const *argv[])
|
||||
case 'z':
|
||||
case 'Z':
|
||||
DontFork = 1;
|
||||
if (*arg == '0') {
|
||||
if (*arg == 'j' || *arg == 'J') {
|
||||
while (*arg) arg++;
|
||||
Daemon = -1;
|
||||
DaemonJSON = 1;
|
||||
} else if (*arg == '0') {
|
||||
PARSENUM(Daemon, arg);
|
||||
if (Daemon == 0) Daemon = -1;
|
||||
else if (Daemon < 1) Daemon = 1;
|
||||
|
||||
101
src/main.c
101
src/main.c
@@ -14,6 +14,8 @@
|
||||
#define _XOPEN_SOURCE 600
|
||||
#include "config.h"
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <sys/wait.h>
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
@@ -45,6 +47,9 @@
|
||||
|
||||
static void DoReminders(void);
|
||||
|
||||
/* Macro for simplifying common block so as not to litter code */
|
||||
#define OUTPUT(c) do { if (output) { DBufPutc(output, c); } else { putchar(c); } } while(0)
|
||||
|
||||
/***************************************************************/
|
||||
/***************************************************************/
|
||||
/** **/
|
||||
@@ -1251,19 +1256,19 @@ int CalcMinsFromUTC(int dse, int tim, int *mins, int *isdst)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static char const *OutputEscapeSequences(char const *s, int print)
|
||||
static char const *OutputEscapeSequences(char const *s, int print, DynamicBuffer *output)
|
||||
{
|
||||
while (*s == 0x1B && *(s+1) == '[') {
|
||||
if (print) putchar(*s);
|
||||
if (print) OUTPUT(*s);
|
||||
s++;
|
||||
if (print) putchar(*s);
|
||||
if (print) OUTPUT(*s);
|
||||
s++;
|
||||
while (*s && (*s < 0x40 || *s > 0x7E)) {
|
||||
if (print) putchar(*s);
|
||||
if (print) OUTPUT(*s);
|
||||
s++;
|
||||
}
|
||||
if (*s) {
|
||||
if (print) putchar(*s);
|
||||
if (print) OUTPUT(*s);
|
||||
s++;
|
||||
}
|
||||
}
|
||||
@@ -1272,19 +1277,19 @@ static char const *OutputEscapeSequences(char const *s, int print)
|
||||
|
||||
#ifdef REM_USE_WCHAR
|
||||
#define ISWBLANK(c) (iswspace(c) && (c) != '\n')
|
||||
static wchar_t const *OutputEscapeSequencesWS(wchar_t const *s, int print)
|
||||
static wchar_t const *OutputEscapeSequencesWS(wchar_t const *s, int print, DynamicBuffer *output)
|
||||
{
|
||||
while (*s == 0x1B && *(s+1) == '[') {
|
||||
if (print) PutWideChar(*s);
|
||||
if (print) PutWideChar(*s, output);
|
||||
s++;
|
||||
if (print) PutWideChar(*s);
|
||||
if (print) PutWideChar(*s, output);
|
||||
s++;
|
||||
while (*s && (*s < 0x40 || *s > 0x7E)) {
|
||||
if (print) PutWideChar(*s);
|
||||
if (print) PutWideChar(*s, output);
|
||||
s++;
|
||||
}
|
||||
if (*s) {
|
||||
if (print) PutWideChar(*s);
|
||||
if (print) PutWideChar(*s, output);
|
||||
s++;
|
||||
}
|
||||
}
|
||||
@@ -1293,7 +1298,7 @@ static wchar_t const *OutputEscapeSequencesWS(wchar_t const *s, int print)
|
||||
|
||||
|
||||
static void
|
||||
FillParagraphWCAux(wchar_t const *s)
|
||||
FillParagraphWCAux(wchar_t const *s, DynamicBuffer *output)
|
||||
{
|
||||
int line = 0;
|
||||
int i, j;
|
||||
@@ -1308,7 +1313,7 @@ FillParagraphWCAux(wchar_t const *s)
|
||||
|
||||
/* If it's a carriage return, output it and start new paragraph */
|
||||
if (*s == '\n') {
|
||||
putchar('\n');
|
||||
OUTPUT('\n');
|
||||
s++;
|
||||
line = 0;
|
||||
while(ISWBLANK(*s)) s++;
|
||||
@@ -1321,7 +1326,7 @@ FillParagraphWCAux(wchar_t const *s)
|
||||
number of spaces */
|
||||
j = line ? SubsIndent : FirstIndent;
|
||||
for (i=0; i<j; i++) {
|
||||
putchar(' ');
|
||||
OUTPUT(' ');
|
||||
}
|
||||
|
||||
/* Calculate the amount of room left on this line */
|
||||
@@ -1334,7 +1339,7 @@ FillParagraphWCAux(wchar_t const *s)
|
||||
if (*s == '\n') break;
|
||||
while(1) {
|
||||
t = s;
|
||||
s = OutputEscapeSequencesWS(s, 1);
|
||||
s = OutputEscapeSequencesWS(s, 1, output);
|
||||
if (s == t) break;
|
||||
while(ISWBLANK(*s)) s++;
|
||||
}
|
||||
@@ -1342,7 +1347,7 @@ FillParagraphWCAux(wchar_t const *s)
|
||||
len = 0;
|
||||
while(*s && !iswspace(*s)) {
|
||||
if (*s == 0x1B && *(s+1) == '[') {
|
||||
s = OutputEscapeSequencesWS(s, 0);
|
||||
s = OutputEscapeSequencesWS(s, 0, output);
|
||||
continue;
|
||||
}
|
||||
len += wcwidth(*s);
|
||||
@@ -1353,17 +1358,17 @@ FillParagraphWCAux(wchar_t const *s)
|
||||
}
|
||||
if (!pendspace || len+pendspace <= roomleft) {
|
||||
for (i=0; i<pendspace; i++) {
|
||||
putchar(' ');
|
||||
OUTPUT(' ');
|
||||
}
|
||||
while(t < s) {
|
||||
PutWideChar(*t);
|
||||
PutWideChar(*t, output);
|
||||
if (strchr(EndSent, *t)) doublespace = 2;
|
||||
else if (!strchr(EndSentIg, *t)) doublespace = 1;
|
||||
t++;
|
||||
}
|
||||
} else {
|
||||
s = t;
|
||||
putchar('\n');
|
||||
OUTPUT('\n');
|
||||
line++;
|
||||
break;
|
||||
}
|
||||
@@ -1374,7 +1379,7 @@ FillParagraphWCAux(wchar_t const *s)
|
||||
}
|
||||
|
||||
static int
|
||||
FillParagraphWC(char const *s)
|
||||
FillParagraphWC(char const *s, DynamicBuffer *output)
|
||||
{
|
||||
size_t len;
|
||||
wchar_t *buf;
|
||||
@@ -1384,7 +1389,7 @@ FillParagraphWC(char const *s)
|
||||
buf = calloc(len+1, sizeof(wchar_t));
|
||||
if (!buf) return E_NO_MEM;
|
||||
(void) mbstowcs(buf, s, len+1);
|
||||
FillParagraphWCAux(buf);
|
||||
FillParagraphWCAux(buf, output);
|
||||
free(buf);
|
||||
return OK;
|
||||
}
|
||||
@@ -1404,7 +1409,7 @@ FillParagraphWC(char const *s)
|
||||
/* A macro safe ONLY if used with arg with no side effects! */
|
||||
#define ISBLANK(c) (isspace(c) && (c) != '\n')
|
||||
|
||||
void FillParagraph(char const *s)
|
||||
void FillParagraph(char const *s, DynamicBuffer *output)
|
||||
{
|
||||
|
||||
int line = 0;
|
||||
@@ -1422,7 +1427,7 @@ void FillParagraph(char const *s)
|
||||
if (!*s) return;
|
||||
|
||||
#ifdef REM_USE_WCHAR
|
||||
if (FillParagraphWC(s) == OK) {
|
||||
if (FillParagraphWC(s, output) == OK) {
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
@@ -1432,7 +1437,7 @@ void FillParagraph(char const *s)
|
||||
|
||||
/* If it's a carriage return, output it and start new paragraph */
|
||||
if (*s == '\n') {
|
||||
putchar('\n');
|
||||
OUTPUT('\n');
|
||||
s++;
|
||||
line = 0;
|
||||
while(ISBLANK(*s)) s++;
|
||||
@@ -1445,7 +1450,7 @@ void FillParagraph(char const *s)
|
||||
number of spaces */
|
||||
j = line ? SubsIndent : FirstIndent;
|
||||
for (i=0; i<j; i++) {
|
||||
putchar(' ');
|
||||
OUTPUT(' ');
|
||||
}
|
||||
|
||||
/* Calculate the amount of room left on this line */
|
||||
@@ -1458,7 +1463,7 @@ void FillParagraph(char const *s)
|
||||
if (*s == '\n') break;
|
||||
while(1) {
|
||||
t = s;
|
||||
s = OutputEscapeSequences(s, 1);
|
||||
s = OutputEscapeSequences(s, 1, output);
|
||||
if (s == t) break;
|
||||
while(ISBLANK(*s)) s++;
|
||||
}
|
||||
@@ -1466,7 +1471,7 @@ void FillParagraph(char const *s)
|
||||
len = 0;
|
||||
while(*s && !isspace(*s)) {
|
||||
if (*s == 0x1B && *(s+1) == '[') {
|
||||
s = OutputEscapeSequences(s, 0);
|
||||
s = OutputEscapeSequences(s, 0, output);
|
||||
continue;
|
||||
}
|
||||
s++;
|
||||
@@ -1477,17 +1482,17 @@ void FillParagraph(char const *s)
|
||||
}
|
||||
if (!pendspace || len+pendspace <= roomleft) {
|
||||
for (i=0; i<pendspace; i++) {
|
||||
putchar(' ');
|
||||
OUTPUT(' ');
|
||||
}
|
||||
while(t < s) {
|
||||
putchar(*t);
|
||||
OUTPUT(*t);
|
||||
if (strchr(EndSent, *t)) doublespace = 2;
|
||||
else if (!strchr(EndSentIg, *t)) doublespace = 1;
|
||||
t++;
|
||||
}
|
||||
} else {
|
||||
s = t;
|
||||
putchar('\n');
|
||||
OUTPUT('\n');
|
||||
line++;
|
||||
break;
|
||||
}
|
||||
@@ -1651,11 +1656,45 @@ SaveLastTimeTrig(TimeTrig const *t)
|
||||
memcpy(&LastTimeTrig, t, sizeof(LastTimeTrig));
|
||||
}
|
||||
|
||||
/* Wrapper to ignore warnings about ignoring return value of system() */
|
||||
/* Wrapper to ignore warnings about ignoring return value of system()
|
||||
Also redirects stdin and stdout to /dev/null for queued reminders */
|
||||
|
||||
void
|
||||
System(char const *cmd)
|
||||
System(char const *cmd, int is_queued)
|
||||
{
|
||||
int r;
|
||||
pid_t kid;
|
||||
int fd;
|
||||
int status;
|
||||
if (is_queued && IsServerMode()) {
|
||||
/* Server mode... redirect stdin and stdout to /dev/null */
|
||||
kid = fork();
|
||||
if (kid == (pid_t) -1) {
|
||||
/* Fork failed... nothing we can do */
|
||||
return;
|
||||
} else if (kid == 0) {
|
||||
/* In the child */
|
||||
(void) close(STDIN_FILENO);
|
||||
(void) close(STDOUT_FILENO);
|
||||
fd = open("/dev/null", O_RDONLY);
|
||||
if (fd >= 0 && fd != STDIN_FILENO) {
|
||||
dup2(fd, STDIN_FILENO);
|
||||
close(STDIN_FILENO);
|
||||
}
|
||||
fd = open("/dev/null", O_WRONLY);
|
||||
if (fd >= 0 && fd != STDOUT_FILENO) {
|
||||
dup2(fd, STDOUT_FILENO);
|
||||
close(STDOUT_FILENO);
|
||||
}
|
||||
} else {
|
||||
/* In the parent */
|
||||
while (waitpid(kid, &status, 0) != kid) {
|
||||
continue;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
/* This is the child process or original if we never forked */
|
||||
r = system(cmd);
|
||||
if (r == 0) {
|
||||
return;
|
||||
|
||||
11
src/protos.h
11
src/protos.h
@@ -38,7 +38,7 @@ int DoRem (ParsePtr p);
|
||||
int DoFlush (ParsePtr p);
|
||||
void DoExit (ParsePtr p);
|
||||
int ParseRem (ParsePtr s, Trigger *trig, TimeTrig *tim, int save_in_globals);
|
||||
int TriggerReminder (ParsePtr p, Trigger *t, TimeTrig *tim, int dse, int is_queued);
|
||||
int TriggerReminder (ParsePtr p, Trigger *t, TimeTrig *tim, int dse, int is_queued, DynamicBuffer *output);
|
||||
int ShouldTriggerReminder (Trigger *t, TimeTrig *tim, int dse, int *err);
|
||||
int DoSubst (ParsePtr p, DynamicBuffer *dbuf, Trigger *t, TimeTrig *tt, int dse, int mode);
|
||||
int DoSubstFromString (char const *source, DynamicBuffer *dbuf, int dse, int tim);
|
||||
@@ -118,7 +118,7 @@ void DestroyVars (int all);
|
||||
int PreserveVar (char const *name);
|
||||
int DoPreserve (Parser *p);
|
||||
int DoSatRemind (Trigger *trig, TimeTrig *tt, ParsePtr p);
|
||||
int DoMsgCommand (char const *cmd, char const *msg);
|
||||
int DoMsgCommand (char const *cmd, char const *msg, int is_queued);
|
||||
int ParseNonSpaceChar (ParsePtr p, int *err, int peek);
|
||||
unsigned int HashVal (char const *str);
|
||||
int DateOK (int y, int m, int d);
|
||||
@@ -142,7 +142,7 @@ int GetSysVar (char const *name, Value *val);
|
||||
int SetSysVar (char const *name, Value *val);
|
||||
void DumpSysVarByName (char const *name);
|
||||
int CalcMinsFromUTC (int dse, int tim, int *mins, int *isdst);
|
||||
void FillParagraph (char const *s);
|
||||
void FillParagraph (char const *s, DynamicBuffer *output);
|
||||
void LocalToUTC (int locdate, int loctime, int *utcdate, int *utctime);
|
||||
void UTCToLocal (int utcdate, int utctime, int *locdate, int *loctime);
|
||||
int MoonPhase (int date, int time);
|
||||
@@ -168,7 +168,7 @@ void PrintJSONKeyPairString(char const *name, char const *val);
|
||||
void PrintJSONKeyPairDate(char const *name, int dse);
|
||||
void PrintJSONKeyPairDateTime(char const *name, int dt);
|
||||
void PrintJSONKeyPairTime(char const *name, int t);
|
||||
void System(char const *cmd);
|
||||
void System(char const *cmd, int queued);
|
||||
int ShellEscape(char const *in, DynamicBuffer *out);
|
||||
int AddGlobalOmit(int dse);
|
||||
void set_lat_and_long_from_components(void);
|
||||
@@ -179,6 +179,7 @@ int GetTerminalBackground(void);
|
||||
char const *get_day_name(int wkday);
|
||||
char const *get_month_name(int mon);
|
||||
|
||||
void set_cloexec(int fd);
|
||||
int push_call(char const *filename, char const *func, int lineno);
|
||||
void clear_callstack(void);
|
||||
int print_callstack(FILE *fp);
|
||||
@@ -190,5 +191,5 @@ void WriteJSONTimeTrigger(TimeTrig const *tt);
|
||||
#define _XOPEN_SOURCE 600
|
||||
#include <wctype.h>
|
||||
#include <wchar.h>
|
||||
void PutWideChar(wchar_t const wc);
|
||||
void PutWideChar(wchar_t const wc, DynamicBuffer *output);
|
||||
#endif
|
||||
|
||||
264
src/queue.c
264
src/queue.c
@@ -24,17 +24,28 @@
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/time.h>
|
||||
#include <time.h>
|
||||
#include <sys/select.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
#include <errno.h>
|
||||
#include "types.h"
|
||||
#include "globals.h"
|
||||
#include "err.h"
|
||||
#include "protos.h"
|
||||
#include "expr.h"
|
||||
|
||||
#undef USE_INOTIFY
|
||||
#if defined(HAVE_SYS_INOTIFY_H) && defined(HAVE_INOTIFY_INIT1)
|
||||
#define USE_INOTIFY 1
|
||||
#include <sys/inotify.h>
|
||||
|
||||
int watch_fd = -1;
|
||||
static void consume_inotify_events(int fd);
|
||||
static int setup_inotify_watch(void);
|
||||
#endif
|
||||
|
||||
/* A list of filenames associated with queued reminders */
|
||||
typedef struct queuedfname {
|
||||
struct queuedfname *next;
|
||||
@@ -73,6 +84,33 @@ static void reread (void);
|
||||
static void PrintQueue(void);
|
||||
static char const *QueueFilename(char const *fname);
|
||||
|
||||
static void chomp(DynamicBuffer *buf)
|
||||
{
|
||||
char *s = DBufValue(buf);
|
||||
int l = DBufLen(buf);
|
||||
while (l) {
|
||||
if (s[l-1] == '\n') {
|
||||
s[l-1] = 0;
|
||||
DBufLen(buf)--;
|
||||
l--;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
char const *SimpleTimeNoSpace(int tim)
|
||||
{
|
||||
char *s = (char *) SimpleTime(tim);
|
||||
if (s && *s) {
|
||||
size_t l = strlen(s);
|
||||
if (l > 0 && s[l-1] == ' ') {
|
||||
s[l-1] = 0;
|
||||
}
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
/***************************************************************/
|
||||
/* */
|
||||
/* QueueFilename */
|
||||
@@ -212,7 +250,14 @@ print_num_queued(void)
|
||||
}
|
||||
q = q->next;
|
||||
}
|
||||
printf("NOTE queued %d\n", nqueued);
|
||||
if (DaemonJSON) {
|
||||
printf("{");
|
||||
PrintJSONKeyPairString("response", "queued");
|
||||
PrintJSONKeyPairInt("nqueued", nqueued);
|
||||
printf("\"command\":\"STATUS\"}\n");
|
||||
} else {
|
||||
printf("NOTE queued %d\n", nqueued);
|
||||
}
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
@@ -233,9 +278,6 @@ void HandleQueuedReminders(void)
|
||||
struct timeval sleep_tv;
|
||||
struct sigaction sa;
|
||||
|
||||
/* Suppress the BANNER from being issued */
|
||||
DidMsgReminder = 1;
|
||||
|
||||
/* Turn off sorting -- otherwise, TriggerReminder has no effect! */
|
||||
SortByDate = 0;
|
||||
|
||||
@@ -285,6 +327,11 @@ void HandleQueuedReminders(void)
|
||||
(void) sigaction(SIGCONT, &sa, NULL);
|
||||
}
|
||||
|
||||
#ifdef USE_INOTIFY
|
||||
if (IsServerMode()) {
|
||||
watch_fd = setup_inotify_watch();
|
||||
}
|
||||
#endif
|
||||
/* Sit in a loop, issuing reminders when necessary */
|
||||
while(1) {
|
||||
q = FindNextReminder();
|
||||
@@ -367,24 +414,44 @@ void HandleQueuedReminders(void)
|
||||
/* Trigger the reminder */
|
||||
CreateParser(q->text, &p);
|
||||
RunDisabled = q->RunDisabled;
|
||||
if (IsServerMode()) {
|
||||
printf("NOTE reminder %s",
|
||||
SimpleTime(q->tt.ttime));
|
||||
printf("%s", SimpleTime(MinutesPastMidnight(1)));
|
||||
if (!*DBufValue(&q->t.tags)) {
|
||||
printf("*\n");
|
||||
} else {
|
||||
printf("%s\n", DBufValue(&(q->t.tags)));
|
||||
}
|
||||
if (IsServerMode() && q->typ != RUN_TYPE) {
|
||||
if (DaemonJSON) {
|
||||
printf("{\"response\":\"reminder\",");
|
||||
PrintJSONKeyPairString("ttime", SimpleTimeNoSpace(q->tt.ttime));
|
||||
PrintJSONKeyPairString("now", SimpleTimeNoSpace(MinutesPastMidnight(1)));
|
||||
PrintJSONKeyPairString("tags", DBufValue(&q->t.tags));
|
||||
} else {
|
||||
printf("NOTE reminder %s",
|
||||
SimpleTime(q->tt.ttime));
|
||||
printf("%s", SimpleTime(MinutesPastMidnight(1)));
|
||||
if (!*DBufValue(&q->t.tags)) {
|
||||
printf("*\n");
|
||||
} else {
|
||||
printf("%s\n", DBufValue(&(q->t.tags)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Set up global variables so some functions like trigdate()
|
||||
and trigtime() work correctly */
|
||||
SaveAllTriggerInfo(&(q->t), &(q->tt), DSEToday, q->tt.ttime, 1);
|
||||
FileName = (char *) q->fname;
|
||||
(void) TriggerReminder(&p, &q->t, &q->tt, DSEToday, 1);
|
||||
if (DaemonJSON) {
|
||||
DynamicBuffer out;
|
||||
DBufInit(&out);
|
||||
(void) TriggerReminder(&p, &q->t, &q->tt, DSEToday, 1, &out);
|
||||
if (q->typ != RUN_TYPE) {
|
||||
printf("\"body\":\"");
|
||||
chomp(&out);
|
||||
PrintJSONString(DBufValue(&out));
|
||||
printf("\"}\n");
|
||||
}
|
||||
DBufFree(&out);
|
||||
} else {
|
||||
(void) TriggerReminder(&p, &q->t, &q->tt, DSEToday, 1, NULL);
|
||||
}
|
||||
FileName = NULL;
|
||||
if (IsServerMode()) {
|
||||
if (IsServerMode() && !DaemonJSON && q->typ != RUN_TYPE) {
|
||||
printf("NOTE endreminder\n");
|
||||
}
|
||||
fflush(stdout);
|
||||
@@ -605,6 +672,9 @@ static void
|
||||
json_queue(QueuedRem const *q)
|
||||
{
|
||||
int done = 0;
|
||||
if (DaemonJSON) {
|
||||
printf("{\"response\":\"queue\",\"queue\":");
|
||||
}
|
||||
printf("[");
|
||||
while(q) {
|
||||
if (q->tt.nexttime == NO_TIME) {
|
||||
@@ -647,7 +717,12 @@ json_queue(QueuedRem const *q)
|
||||
printf("\"}");
|
||||
q = q->next;
|
||||
}
|
||||
printf("]\n");
|
||||
printf("]");
|
||||
if (DaemonJSON) {
|
||||
printf(",\"command\":\"QUEUE\"}\n");
|
||||
} else {
|
||||
printf("\n");
|
||||
}
|
||||
}
|
||||
|
||||
/***************************************************************/
|
||||
@@ -662,15 +737,28 @@ static void DaemonWait(struct timeval *sleep_tv)
|
||||
fd_set readSet;
|
||||
int retval;
|
||||
int y, m, d;
|
||||
int max = 1;
|
||||
char cmdLine[256];
|
||||
|
||||
FD_ZERO(&readSet);
|
||||
FD_SET(0, &readSet);
|
||||
retval = select(1, &readSet, NULL, NULL, sleep_tv);
|
||||
|
||||
#ifdef USE_INOTIFY
|
||||
if (watch_fd >= 0) {
|
||||
FD_SET(watch_fd, &readSet);
|
||||
if (watch_fd > max-1)
|
||||
max = watch_fd+1;
|
||||
}
|
||||
#endif
|
||||
retval = select(max, &readSet, NULL, NULL, sleep_tv);
|
||||
|
||||
/* If date has rolled around, restart */
|
||||
if (RealToday != SystemDate(&y, &m, &d)) {
|
||||
printf("NOTE newdate\nNOTE reread\n");
|
||||
if (DaemonJSON) {
|
||||
printf("{\"response\":\"newdate\"}\n{\"response\":\"reread\",\"command\":\"newdate\"}\n");
|
||||
} else {
|
||||
printf("NOTE newdate\nNOTE reread\n");
|
||||
}
|
||||
fflush(stdout);
|
||||
reread();
|
||||
}
|
||||
@@ -678,6 +766,24 @@ static void DaemonWait(struct timeval *sleep_tv)
|
||||
/* If nothing readable or interrupted system call, return */
|
||||
if (retval <= 0) return;
|
||||
|
||||
/* If inotify watch descriptor is readable, handle it */
|
||||
#ifdef USE_INOTIFY
|
||||
if (watch_fd >= 0) {
|
||||
if (FD_ISSET(watch_fd, &readSet)) {
|
||||
consume_inotify_events(watch_fd);
|
||||
if (DaemonJSON) {
|
||||
printf("{\"response\":\"reread\",\"command\":\"inotify\"}\n");
|
||||
} else {
|
||||
/* In deprecated server mode, we need to spit out
|
||||
a NOTE newdate to force the front-end to redraw
|
||||
the calendar */
|
||||
printf("NOTE newdate\nNOTE reread\n");
|
||||
}
|
||||
fflush(stdout);
|
||||
reread();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
/* If stdin not readable, return */
|
||||
if (!FD_ISSET(0, &readSet)) return;
|
||||
|
||||
@@ -696,43 +802,65 @@ static void DaemonWait(struct timeval *sleep_tv)
|
||||
} else if (!strcmp(cmdLine, "STATUS\n")) {
|
||||
print_num_queued();
|
||||
} else if (!strcmp(cmdLine, "QUEUE\n")) {
|
||||
printf("NOTE queue\n");
|
||||
QueuedRem *q = QueueHead;
|
||||
while (q) {
|
||||
if (q->tt.nexttime != NO_TIME) {
|
||||
switch (q->typ) {
|
||||
case NO_TYPE: printf("NO_TYPE "); break;
|
||||
case MSG_TYPE: printf("MSG_TYPE "); break;
|
||||
case RUN_TYPE: printf("RUN_TYPE "); break;
|
||||
case CAL_TYPE: printf("CAL_TYPE "); break;
|
||||
case SAT_TYPE: printf("SAT_TYPE "); break;
|
||||
case PS_TYPE: printf("PS_TYPE "); break;
|
||||
case PSF_TYPE: printf("PSF_TYPE "); break;
|
||||
case MSF_TYPE: printf("MSF_TYPE "); break;
|
||||
case PASSTHRU_TYPE: printf("PASSTHRU_TYPE "); break;
|
||||
default: printf("? "); break;
|
||||
}
|
||||
printf("RunDisabled=%d ntrig=%d ttime=%02d:%02d nexttime=%02d:%02d delta=%d rep=%d duration=%d ", q->RunDisabled, q->ntrig, q->tt.ttime/60, q->tt.ttime % 60, q->tt.nexttime / 60, q->tt.nexttime % 60, q->tt.delta, (q->tt.rep != NO_TIME ? q->tt.rep : -1), (q->tt.duration != NO_TIME ? q->tt.duration : -1));
|
||||
printf("%s %s %s\n",
|
||||
(q->passthru[0] ? q->passthru : "*"),
|
||||
(q->sched[0] ? q->sched : "*"),
|
||||
q->text ? q->text : "NULL");
|
||||
if (DaemonJSON) {
|
||||
json_queue(QueueHead);
|
||||
} else {
|
||||
printf("NOTE queue\n");
|
||||
QueuedRem *q = QueueHead;
|
||||
while (q) {
|
||||
if (q->tt.nexttime != NO_TIME) {
|
||||
switch (q->typ) {
|
||||
case NO_TYPE: printf("NO_TYPE"); break;
|
||||
case MSG_TYPE: printf("MSG_TYPE"); break;
|
||||
case RUN_TYPE: printf("RUN_TYPE"); break;
|
||||
case CAL_TYPE: printf("CAL_TYPE"); break;
|
||||
case SAT_TYPE: printf("SAT_TYPE"); break;
|
||||
case PS_TYPE: printf("PS_TYPE"); break;
|
||||
case PSF_TYPE: printf("PSF_TYPE"); break;
|
||||
case MSF_TYPE: printf("MSF_TYPE"); break;
|
||||
case PASSTHRU_TYPE: printf("PASSTHRU_TYPE"); break;
|
||||
default: printf("?"); break;
|
||||
}
|
||||
printf(" RunDisabled=%d ntrig=%d ttime=%02d:%02d nexttime=%02d:%02d delta=%d rep=%d duration=%d ", q->RunDisabled, q->ntrig, q->tt.ttime/60, q->tt.ttime % 60, q->tt.nexttime / 60, q->tt.nexttime % 60, q->tt.delta, (q->tt.rep != NO_TIME ? q->tt.rep : -1), (q->tt.duration != NO_TIME ? q->tt.duration : -1));
|
||||
printf("%s %s %s\n",
|
||||
(q->passthru[0] ? q->passthru : "*"),
|
||||
(q->sched[0] ? q->sched : "*"),
|
||||
q->text ? q->text : "NULL");
|
||||
}
|
||||
q = q->next;
|
||||
}
|
||||
q = q->next;
|
||||
printf("NOTE endqueue\n");
|
||||
}
|
||||
printf("NOTE endqueue\n");
|
||||
fflush(stdout);
|
||||
} else if (!strcmp(cmdLine, "JSONQUEUE\n")) {
|
||||
printf("NOTE JSONQUEUE\n");
|
||||
if (!DaemonJSON) {
|
||||
printf("NOTE JSONQUEUE\n");
|
||||
}
|
||||
json_queue(QueueHead);
|
||||
printf("NOTE ENDJSONQUEUE\n");
|
||||
if (!DaemonJSON) {
|
||||
printf("NOTE ENDJSONQUEUE\n");
|
||||
}
|
||||
fflush(stdout);
|
||||
} else if (!strcmp(cmdLine, "REREAD\n")) {
|
||||
printf("NOTE reread\n");
|
||||
if (DaemonJSON) {
|
||||
printf("{\"response\":\"reread\",\"command\":\"REREAD\"}\n");
|
||||
} else {
|
||||
printf("NOTE reread\n");
|
||||
}
|
||||
fflush(stdout);
|
||||
reread();
|
||||
} else {
|
||||
printf("ERR Invalid daemon command: %s", cmdLine);
|
||||
if (DaemonJSON) {
|
||||
size_t l = strlen(cmdLine);
|
||||
if (l && cmdLine[l-1] == '\n') {
|
||||
cmdLine[l-1] = 0;
|
||||
}
|
||||
printf("{\"response\":\"error\",\"error\":\"Unknown command\",\"command\":\"");
|
||||
PrintJSONString(cmdLine);
|
||||
printf("\"}\n");
|
||||
} else {
|
||||
printf("ERR Invalid daemon command: %s", cmdLine);
|
||||
}
|
||||
fflush(stdout);
|
||||
}
|
||||
}
|
||||
@@ -749,3 +877,47 @@ static void reread(void)
|
||||
execvp(ArgV[0], (char **) ArgV);
|
||||
}
|
||||
|
||||
#ifdef USE_INOTIFY
|
||||
static void consume_inotify_events(int fd)
|
||||
{
|
||||
char buf[sizeof(struct inotify_event) + NAME_MAX + 1];
|
||||
int n;
|
||||
|
||||
struct timespec sleeptime;
|
||||
/* HACK: sleep for 0.2 seconds to let multiple events queue up so we
|
||||
only do a single reread */
|
||||
sleeptime.tv_sec = 0;
|
||||
sleeptime.tv_nsec = 200000000;
|
||||
nanosleep(&sleeptime, NULL);
|
||||
|
||||
/* Consume all the inotify events */
|
||||
while(1) {
|
||||
n = read(fd, buf, sizeof(buf));
|
||||
if (n < 0) {
|
||||
if (errno == EINTR) continue;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int setup_inotify_watch(void)
|
||||
{
|
||||
int fd;
|
||||
|
||||
/* Don't inotify_watch stdin */
|
||||
if (!strcmp(InitialFile, "-")) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
fd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC);
|
||||
if (fd < 0) {
|
||||
return fd;
|
||||
}
|
||||
if (inotify_add_watch(fd, InitialFile, IN_CLOSE_WRITE | IN_CREATE | IN_DELETE | IN_MODIFY | IN_MOVED_FROM | IN_MOVED_TO) < 0) {
|
||||
close(fd);
|
||||
return -1;
|
||||
}
|
||||
return fd;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -136,7 +136,7 @@ void IssueSortedReminders(void)
|
||||
switch(cur->typ) {
|
||||
case MSG_TYPE:
|
||||
if (MsgCommand && *MsgCommand) {
|
||||
DoMsgCommand(MsgCommand, cur->text);
|
||||
DoMsgCommand(MsgCommand, cur->text, 0);
|
||||
} else {
|
||||
if (cur->trigdate != olddate) {
|
||||
IssueSortBanner(cur->trigdate);
|
||||
@@ -151,11 +151,11 @@ void IssueSortedReminders(void)
|
||||
IssueSortBanner(cur->trigdate);
|
||||
olddate = cur->trigdate;
|
||||
}
|
||||
FillParagraph(cur->text);
|
||||
FillParagraph(cur->text, NULL);
|
||||
break;
|
||||
|
||||
case RUN_TYPE:
|
||||
System(cur->text);
|
||||
System(cur->text, 0);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -72,7 +72,10 @@ static int NextSimpleTrig(int startdate, Trigger *trig, int *err)
|
||||
m++;
|
||||
if (m == 12) { m = 0; y++; }
|
||||
}
|
||||
while (trig->d > DaysInMonth(m, trig->y)) m++;
|
||||
while (trig->d > DaysInMonth(m, y)) {
|
||||
m++;
|
||||
if (m == 12) { m = 0; y++; }
|
||||
}
|
||||
j = DSE(y, m, trig->d);
|
||||
return j;
|
||||
|
||||
|
||||
@@ -453,6 +453,91 @@ rm -rf include_dir/ww
|
||||
# date, we have to convert it to some constant (in this case,
|
||||
# VOLATILE) so that tests are not dependent on the system date.
|
||||
echo JSONQUEUE | ../src/remind -z0 ../tests/queue1.rem 2>&1 | sed -e 's/"eventstart":"................"/"eventstart":"VOLATILE"/g' >> ../tests/test.out 2>&1
|
||||
echo QUEUE | ../src/remind -zj ../tests/queue1.rem 2>&1 | sed -e 's/"eventstart":"................"/"eventstart":"VOLATILE"/g' >> ../tests/test.out 2>&1
|
||||
|
||||
# Test for leap year bug that was fixed
|
||||
../src/remind -dte - 28 Feb 2024 <<'EOF' >> ../tests/test.out 2>&1
|
||||
BANNER %
|
||||
REM 29 MSG One
|
||||
REM 29 Feb MSG two
|
||||
REM 29 2024 MSG three
|
||||
REM 29 Feb 2024 MSG four
|
||||
REM Thursday 29 MSG One
|
||||
REM Thursday 29 Feb MSG two
|
||||
REM Thursday 29 2024 MSG three
|
||||
REM Thursday 29 Feb 2024 MSG four
|
||||
REM Wednesday 29 MSG One
|
||||
REM Wednesday 29 Feb MSG two
|
||||
REM Wednesday 29 2024 MSG three
|
||||
REM Wednesday 29 Feb 2024 MSG four
|
||||
REM Friday 29 MSG One
|
||||
REM Friday 29 Feb MSG two
|
||||
REM Friday 29 2024 MSG three
|
||||
REM Friday 29 Feb 2024 MSG four
|
||||
EOF
|
||||
|
||||
../src/remind -dte - 1 Mar 2024 <<'EOF' >> ../tests/test.out 2>&1
|
||||
BANNER %
|
||||
REM 29 MSG One
|
||||
REM 29 Feb MSG two
|
||||
REM 29 2024 MSG three
|
||||
REM 29 Feb 2024 MSG four
|
||||
REM Thursday 29 MSG One
|
||||
REM Thursday 29 Feb MSG two
|
||||
REM Thursday 29 2024 MSG three
|
||||
REM Thursday 29 Feb 2024 MSG four
|
||||
REM Wednesday 29 MSG One
|
||||
REM Wednesday 29 Feb MSG two
|
||||
REM Wednesday 29 2024 MSG three
|
||||
REM Wednesday 29 Feb 2024 MSG four
|
||||
REM Friday 29 MSG One
|
||||
REM Friday 29 Feb MSG two
|
||||
REM Friday 29 2024 MSG three
|
||||
REM Friday 29 Feb 2024 MSG four
|
||||
EOF
|
||||
|
||||
../src/remind -dte - 28 Feb 2025 <<'EOF' >> ../tests/test.out 2>&1
|
||||
BANNER %
|
||||
REM 29 MSG One
|
||||
REM 29 Feb MSG two
|
||||
REM 29 2025 MSG three
|
||||
REM 29 Feb 2025 MSG four
|
||||
REM Thursday 29 MSG One
|
||||
REM Thursday 29 Feb MSG two
|
||||
REM Thursday 29 2025 MSG three
|
||||
REM Thursday 29 Feb 2025 MSG four
|
||||
REM Wednesday 29 MSG One
|
||||
REM Wednesday 29 Feb MSG two
|
||||
REM Wednesday 29 2025 MSG three
|
||||
REM Wednesday 29 Feb 2025 MSG four
|
||||
REM Friday 29 MSG One
|
||||
REM Friday 29 Feb MSG two
|
||||
REM Friday 29 2025 MSG three
|
||||
REM Friday 29 Feb 2025 MSG four
|
||||
EOF
|
||||
|
||||
../src/remind -dte - 1 Mar 2025 <<'EOF' >> ../tests/test.out 2>&1
|
||||
BANNER %
|
||||
REM 29 MSG One
|
||||
REM 29 Feb MSG two
|
||||
REM 29 2025 MSG three
|
||||
REM 29 Feb 2025 MSG four
|
||||
REM Thursday 29 MSG One
|
||||
REM Thursday 29 Feb MSG two
|
||||
REM Thursday 29 2025 MSG three
|
||||
REM Thursday 29 Feb 2025 MSG four
|
||||
REM Wednesday 29 MSG One
|
||||
REM Wednesday 29 Feb MSG two
|
||||
REM Wednesday 29 2025 MSG three
|
||||
REM Wednesday 29 Feb 2025 MSG four
|
||||
REM Friday 29 MSG One
|
||||
REM Friday 29 Feb MSG two
|
||||
REM Friday 29 2025 MSG three
|
||||
REM Friday 29 Feb 2025 MSG four
|
||||
EOF
|
||||
|
||||
|
||||
(echo 'BANNER %'; echo 'REM 29 MSG No bug') | ../src/remind -dt - >> ../tests/test.out 2>&1
|
||||
|
||||
# Remove references to SysInclude, which is build-specific
|
||||
grep -F -v '$SysInclude' < ../tests/test.out > ../tests/test.out.1 && mv -f ../tests/test.out.1 ../tests/test.out
|
||||
|
||||
1311
tests/test.cmp
1311
tests/test.cmp
File diff suppressed because one or more lines are too long
@@ -381,9 +381,11 @@ msg [a076]%
|
||||
set a077 dosubst("%*Y %*Z", '1992/5/5')
|
||||
msg [a077]%
|
||||
set a078 easterdate(today())
|
||||
set a078 easterdate()
|
||||
set a079 easterdate(1992)
|
||||
set a080 easterdate(1995)
|
||||
set a078 orthodoxeaster(today())
|
||||
set a078 orthodoxeaster()
|
||||
set a079 orthodoxeaster(1992)
|
||||
set a080 orthodoxeaster(1995)
|
||||
set a080 orthodoxeaster(2023)
|
||||
|
||||
@@ -1,341 +0,0 @@
|
||||
<?php
|
||||
|
||||
class Remind
|
||||
{
|
||||
# For validating commands we send to popen
|
||||
function is_valid_day($d) {
|
||||
return (preg_match('/^\d+$/', $d)) &&
|
||||
$d >= 1 && $d <= 31;
|
||||
}
|
||||
|
||||
function is_valid_month($m) {
|
||||
return
|
||||
($m == 'January') ||
|
||||
($m == 'February') ||
|
||||
($m == 'March') ||
|
||||
($m == 'April') ||
|
||||
($m == 'May') ||
|
||||
($m == 'June') ||
|
||||
($m == 'July') ||
|
||||
($m == 'August') ||
|
||||
($m == 'September') ||
|
||||
($m == 'October') ||
|
||||
($m == 'November') ||
|
||||
($m == 'December');
|
||||
}
|
||||
|
||||
function is_valid_year($y) {
|
||||
return preg_match('/^\d\d\d\d$/', $y) &&
|
||||
$y >= 1900;
|
||||
}
|
||||
|
||||
|
||||
function get_el(&$array, $i)
|
||||
{
|
||||
if (!array_key_exists($i, $array)) return null;
|
||||
return $array[$i];
|
||||
}
|
||||
|
||||
function get_elem($array, $indexes)
|
||||
{
|
||||
foreach ($indexes as $i) {
|
||||
if (!is_array($array)) return null;
|
||||
if (!array_key_exists($i, $array)) return null;
|
||||
$array = $array[$i];
|
||||
}
|
||||
return $array;
|
||||
}
|
||||
|
||||
function munge_entry($day, &$results, &$specials, &$options, $str, &$e) {
|
||||
return htmlspecialchars($str);
|
||||
}
|
||||
|
||||
function format_entry($day, &$results, &$specials, &$options, &$e) {
|
||||
$special = $this->get_el($e, 'special');
|
||||
$body = $this->get_el($e, 'body');
|
||||
|
||||
if ($body === null) $body = '';
|
||||
if ($special === null || $special == '*') {
|
||||
return $this->munge_entry($day, $results, $specials, $options, $body, $e);
|
||||
}
|
||||
if ($special == 'COLOR' || $special == 'COLOUR') {
|
||||
if (preg_match('/^(\d+)\s+(\d+)\s+(\d+)\s+(.*)/', $body, $matches)) {
|
||||
return sprintf('<span style="color: #%02x%02x%02x">%s</span>',
|
||||
$matches[1] % 255,
|
||||
$matches[2] % 255,
|
||||
$matches[3] % 255,
|
||||
$this->munge_entry($day, $results, $specials, $options, $matches[4], $e));
|
||||
}
|
||||
return 'Bad COLOR spec: ' . htmlspecialchars($body);
|
||||
}
|
||||
|
||||
# HTML is passed through un-munged.
|
||||
if ($special == 'HTML') return $body;
|
||||
|
||||
# Ignore unknown specials
|
||||
return '';
|
||||
}
|
||||
|
||||
function format_entries($day, &$results, &$specials, &$options, &$entries) {
|
||||
$html = '';
|
||||
foreach ($entries as $e) {
|
||||
$html .= '<div class="rem-entry">' . $this->format_entry($day, $results, $specials, $options, $e) . '</div>';
|
||||
}
|
||||
return $html;
|
||||
}
|
||||
|
||||
function do_one_day($day, &$results, &$specials, &$options) {
|
||||
$class = $this->get_elem($specials, array('HTMLCLASS', $day, 0, 'body'));
|
||||
$shade = $this->get_elem($specials, array('SHADE', $day, 0, 'body'));
|
||||
$moon = $this->get_elem($specials, array('MOON', $day, 0, 'body'));
|
||||
|
||||
if ($class === null) $class = 'rem-cell';
|
||||
$bg = '';
|
||||
if ($shade !== null) {
|
||||
if (preg_match('/(\d+)\s+(\d+)\s+(\d+)/', $shade, $matches)) {
|
||||
if ($matches[1] <= 255 && $matches[2] <= 255 && $matches[3] <= 255) {
|
||||
$bg = sprintf(' style="background: #%02x%02x%02x"',
|
||||
$matches[1], $matches[2], $matches[3]);
|
||||
}
|
||||
}
|
||||
}
|
||||
$html = "<td class=\"$class\"$bg>";
|
||||
|
||||
$week = $this->get_elem($specials, array('WEEK', $day, 0, 'body'));
|
||||
if ($week === null) {
|
||||
$week = '';
|
||||
} else {
|
||||
$week = ' ' . $week;
|
||||
}
|
||||
|
||||
$moon_html = '';
|
||||
if ($moon !== null) {
|
||||
$phase = -1;
|
||||
if (preg_match('/(\d+)\s+(\S+)\s+(\S+)\s+(.*)$/', $moon, $matches)) {
|
||||
$phase = $matches[1];
|
||||
$moonsize = $matches[2];
|
||||
$fontsize = $matches[3];
|
||||
$msg = $matches[4];
|
||||
} elseif (preg_match('/(\d+)/', $moon, $matches)) {
|
||||
$phase = $matches[1];
|
||||
$msg = '';
|
||||
}
|
||||
if ($phase >= 0) {
|
||||
if ($phase == 0) {
|
||||
$img = 'newmoon.png';
|
||||
$title = 'New Moon';
|
||||
$alt = 'new';
|
||||
} elseif ($phase == 1) {
|
||||
$img = 'firstquarter.png';
|
||||
$title = 'First Quarter';
|
||||
$alt = '1st';
|
||||
} elseif ($phase == 2) {
|
||||
$img = 'fullmoon.png';
|
||||
$alt = 'full';
|
||||
$title = 'Full Moon';
|
||||
} else {
|
||||
$img = 'lastquarter.png';
|
||||
$alt = 'last';
|
||||
$title = 'Last Quarter';
|
||||
}
|
||||
$base = rtrim($this->get_el($options, 'imgbase'), '/');
|
||||
if ($base !== null) {
|
||||
$img = $base . '/' . $img;
|
||||
}
|
||||
$moon_html = '<div class="rem-moon">' . "<img width=\"16\" height=\"16\" alt=\"$alt\" title=\"$title\" src=\"$img\">" . htmlspecialchars($msg) . '</div>';
|
||||
}
|
||||
}
|
||||
|
||||
# Day number
|
||||
$html .= $moon_html . '<div class="rem-daynumber">' . $day . $week . '</div>';
|
||||
|
||||
# And the entries
|
||||
$entries = $this->get_elem($results, array('entries', $day));
|
||||
if (is_array($entries) && count($entries) > 0) {
|
||||
$html .= '<div class="rem-entries">';
|
||||
$html .= $this->format_entries($day, $results, $specials, $options, $entries);
|
||||
$html .= '</div>';
|
||||
}
|
||||
$html .= "</td>\n";
|
||||
return $html;
|
||||
}
|
||||
|
||||
function small_calendar($results, $month, $monlen, $first_col, $which, &$options)
|
||||
{
|
||||
$monday_first = $results['monday_flag'];
|
||||
if ($monday_first) {
|
||||
$first_col--;
|
||||
if ($first_col < 0) {
|
||||
$first_col = 6;
|
||||
}
|
||||
}
|
||||
|
||||
$html = "<td class=\"rem-small-calendar\">\n<table class=\"rem-sc-table\">\n<caption class=\"rem-sc-caption\">";
|
||||
# TODO: URL for small calendar
|
||||
$html .= $month;
|
||||
$html .= "</caption>\n";
|
||||
}
|
||||
|
||||
function generate_html(&$results, &$specials, &$options)
|
||||
{
|
||||
$monday_first = $results['monday_flag'];
|
||||
$first_col = $results['first_day'];
|
||||
if ($monday_first) {
|
||||
$first_col--;
|
||||
if ($first_col < 0) $first_col = 6;
|
||||
}
|
||||
|
||||
$last_col = ($first_col + $results['days_in_mon'] -1) % 7;
|
||||
|
||||
$html = '<table class="rem-cal"><caption class="rem-cal-caption">' .
|
||||
htmlspecialchars($results['month']) . ' ' . htmlspecialchars($results['year']) .
|
||||
"</caption>\n";
|
||||
|
||||
$html .= '<tr class="rem-cal-hdr-row">';
|
||||
if (!$monday_first) $html .= '<th class="rem-cal-hdr">' . htmlspecialchars($results['day_names'][0]) . '</th>';
|
||||
for ($i=1; $i<7; $i++) $html .= '<th class="rem-cal-hdr">' . htmlspecialchars($results['day_names'][$i]) . '</th>';
|
||||
if ($monday_first) $html .= '<th class="rem-cal-hdr">' . htmlspecialchars($results['day_names'][0]) . '</th>';
|
||||
$html .= "</tr>\n";
|
||||
|
||||
# Do the leading empty columns
|
||||
for ($col=0; $col < $first_col; $col++) {
|
||||
if ($col == 0) $html .= '<tr class="rem-cal-body-row">';
|
||||
$html .= '<td class="rem-empty"> </td>';
|
||||
}
|
||||
|
||||
for ($day=1; $day <= $results['days_in_mon']; $day++) {
|
||||
if ($col == 0) $html .= '<tr class="rem-cal-body-row">';
|
||||
$col++;
|
||||
$html .= $this->do_one_day($day, $results, $specials, $options);
|
||||
if ($col == 7) {
|
||||
$html .= "</tr>\n";
|
||||
$col = 0;
|
||||
}
|
||||
}
|
||||
if ($col) {
|
||||
while ($col++ < 7) {
|
||||
$html .= '<td class="rem-empty"> </td>';
|
||||
}
|
||||
}
|
||||
$html .= "</tr>\n";
|
||||
|
||||
$html .= "</table>\n";
|
||||
return $html;
|
||||
}
|
||||
function parse_remind_output ($fp)
|
||||
{
|
||||
while(1) {
|
||||
$line = fgets($fp);
|
||||
if ($line === false) break;
|
||||
$line = trim($line);
|
||||
if ($line == '# rem2ps begin') break;
|
||||
}
|
||||
if ($line === false) {
|
||||
return array('success' => 0,
|
||||
'error' => 'Could not find any Rem2PS data');
|
||||
}
|
||||
|
||||
$line = fgets($fp);
|
||||
if ($line === false) {
|
||||
return array('success' => 0,
|
||||
'error' => 'Unexpected end-of-file');
|
||||
}
|
||||
|
||||
$line = trim($line);
|
||||
list($month, $year, $days_in_mon, $first_day, $monday_flag) = explode(' ', $line);
|
||||
$retval = array('month' => $month,
|
||||
'year' => $year,
|
||||
'days_in_mon' => $days_in_mon,
|
||||
'first_day' => $first_day,
|
||||
'monday_flag' => $monday_flag);
|
||||
|
||||
$line = fgets($fp);
|
||||
if ($line === false) {
|
||||
return array('success' => 0,
|
||||
'error' => 'Unexpected end-of-file');
|
||||
}
|
||||
|
||||
$line = trim($line);
|
||||
$retval['day_names'] = explode(' ', $line);
|
||||
|
||||
$line = fgets($fp);
|
||||
if ($line === false) {
|
||||
return array('success' => 0,
|
||||
'error' => 'Unexpected end-of-file');
|
||||
}
|
||||
$line = trim($line);
|
||||
|
||||
list($m, $n) = explode(' ', $line);
|
||||
$retval['prev'] = array('month' => $m, 'days' => $n);
|
||||
|
||||
$line = fgets($fp);
|
||||
if ($line === false) {
|
||||
return array('success' => 0,
|
||||
'error' => 'Unexpected end-of-file');
|
||||
}
|
||||
$line = trim($line);
|
||||
|
||||
list($m, $n) = explode(' ', $line);
|
||||
$retval['next'] = array('month' => $m, 'days' => $n);
|
||||
|
||||
$line_info = 0;
|
||||
|
||||
$entries = array();
|
||||
$specials = array();
|
||||
while (1) {
|
||||
$line = fgets($fp);
|
||||
if ($line === false) break;
|
||||
$line = trim($line);
|
||||
if ($line == '# rem2ps end') break;
|
||||
if (strpos($line, '# fileinfo ') === 0) {
|
||||
list($lno, $fname) = explode(' ', substr($line, 11), 2);
|
||||
$lineinfo = array('file' => $fname, 'line' => $lno);
|
||||
continue;
|
||||
}
|
||||
list($date, $special, $tags, $duration, $time, $body) = explode(' ', $line, 6);
|
||||
list($y, $m, $d) = explode('/', $date);
|
||||
$d = preg_replace('/^0(.)/', '$1', $d);
|
||||
$m = preg_replace('/^0(.)/', '$1', $m);
|
||||
$entry = array('day' => $d,
|
||||
'month' => $m,
|
||||
'year' => $y,
|
||||
'special' => $special,
|
||||
'tags' => $tags,
|
||||
'duration' => $duration,
|
||||
'time' => $time,
|
||||
'body' => $body);
|
||||
if (is_array($lineinfo)) {
|
||||
$entry['line'] = $lineinfo['line'];
|
||||
$entry['file'] = $lineinfo['file'];
|
||||
$lineinfo = 0;
|
||||
}
|
||||
if ($special != '*' && $special != 'COLOR' && $special != 'COLOUR' && $special != 'HTML') {
|
||||
if (!array_key_exists($special, $specials)) {
|
||||
$specials[$special] = array();
|
||||
}
|
||||
if (!array_key_exists($d, $specials[$special])) {
|
||||
$specials[$special][$d] = array();
|
||||
}
|
||||
$specials[$special][$d][] = $entry;
|
||||
} else {
|
||||
if (!array_key_exists($d, $entries)) {
|
||||
$entries[$d] = array();
|
||||
}
|
||||
$entries[$d][] = $entry;
|
||||
}
|
||||
|
||||
}
|
||||
$retval['entries'] = $entries;
|
||||
return array('success' => 1, 'results' => $retval, 'specials' => $specials);
|
||||
}
|
||||
}
|
||||
|
||||
$fp = popen('rem -p -l', 'r');
|
||||
$r = new Remind;
|
||||
$ans = $r->parse_remind_output($fp);
|
||||
pclose($fp);
|
||||
print_r($ans);
|
||||
$options = array();
|
||||
#print $r->generate_html($ans['results'], $ans['specials'], $options);
|
||||
|
||||
?>
|
||||
Reference in New Issue
Block a user