Compare commits

...

33 Commits

Author SHA1 Message Date
Dianne Skoll
6140221bf3 Make a function static. 2025-02-01 11:15:52 -05:00
Dianne Skoll
51b831fb6a Check for proper escaping in JSON and TRANSLATE DUMP. 2025-02-01 11:15:25 -05:00
Dianne Skoll
35a4994b3e Document changes. 2025-02-01 10:59:04 -05:00
Dianne Skoll
0ebaaa4097 Add optional add_quote argument to escape() 2025-02-01 10:58:55 -05:00
Dianne Skoll
2f43aca21c Update version to 05.03.00 2025-02-01 10:58:34 -05:00
Dianne Skoll
930bab0fde Add more tests. 2025-02-01 10:45:34 -05:00
Dianne Skoll
694c4099d1 Add \xAA sequence for parsing quoted strings; add the escape() built-in function; update docs. 2025-02-01 10:36:38 -05:00
Dianne Skoll
ca56b4c90e Disallow "\x00" 2025-02-01 10:12:51 -05:00
Dianne Skoll
5c965e2083 Add "\xAB" escapes to string parser.
\x followed by one or two hex digits gets converted to that character.
2025-02-01 10:08:12 -05:00
Dianne Skoll
d58ccbef69 Improve how add/edit reminder dialog resizes.
All checks were successful
Remind unit tests / tests (push) Successful in 37s
2025-01-31 21:31:47 -05:00
Dianne Skoll
17ad03be69 Add a bit of space after labels. 2025-01-31 21:22:48 -05:00
Dianne Skoll
75a4e98de2 Call the reminder body the "Summary" rather than "Subject" to be consistent with ical. 2025-01-31 21:21:30 -05:00
Dianne Skoll
1408f77303 Use concat instead of list to flatten lists.
Bug report: https://dianne.skoll.ca/pipermail/remind-fans/2025/004986.html
2025-01-31 19:36:41 -05:00
Dianne Skoll
af76dd67fb Remove dead code; count lines better. 2025-01-31 17:02:03 -05:00
Dianne Skoll
f7a19d1570 Change "Body:" to "Subject:" and update man page. 2025-01-31 16:48:48 -05:00
Dianne Skoll
e7ec975ff0 Support location and description fields in tkremind. 2025-01-31 16:30:07 -05:00
Dianne Skoll
8c4ca12ca7 When creating the "info" JSON hash, make the keys lower-case instead of upper-case.
All checks were successful
Remind unit tests / tests (push) Successful in 46s
2025-01-31 08:07:53 -05:00
Dianne Skoll
e832eb868c Make INFO require "Header: Value" strings; make the "info" element in the JSON output a hash instead of an array.
All checks were successful
Remind unit tests / tests (push) Successful in 36s
2025-01-30 16:58:56 -05:00
Dianne Skoll
cb0acb3077 Document INFO
All checks were successful
Remind unit tests / tests (push) Successful in 54s
2025-01-29 19:07:26 -05:00
Dianne Skoll
9376c7a36d Add INFO keyword. 2025-01-29 18:55:22 -05:00
Dianne Skoll
e6ceeee2ec Add the "INFO" clause to the REM command.
All checks were successful
Remind unit tests / tests (push) Successful in 33s
Intended to pass additional information to a back-end to use as it wishes.
One example is to add extra info such as locaiton, description, etc. to ical
files.
2025-01-28 15:51:28 -05:00
Dianne Skoll
bbeece644e Use "custom.h" rather than <custom.h> for our header file. 2025-01-24 08:10:06 -05:00
Dianne Skoll
8d09abc363 Use snprintf in favor of sprintf almost everywhere.
All checks were successful
Remind unit tests / tests (push) Successful in 35s
2025-01-22 11:11:08 -05:00
Dianne Skoll
3dcd353fb5 Update release notes. 2025-01-22 10:56:05 -05:00
Dianne Skoll
124c5c4e7e Make test mode warning more verbose.
All checks were successful
Remind unit tests / tests (push) Successful in 35s
2025-01-21 11:54:24 -05:00
Dianne Skoll
77024562b3 Fix SystemDate to always return 2025-01-06 in --test mode.
All checks were successful
Remind unit tests / tests (push) Successful in 38s
2025-01-21 11:52:20 -05:00
Dianne Skoll
35c33ae915 Prevent infinite test loop. 2025-01-21 11:48:56 -05:00
Dianne Skoll
901831ff75 Add --test long option to make the test suite repeatable.
Some checks failed
Remind unit tests / tests (push) Failing after 3h14m50s
2025-01-20 13:33:00 -05:00
Dianne Skoll
e0c5e878a8 Explicitly unset REMIND_RUNNING_TEST at the start. 2025-01-20 11:35:32 -05:00
Dianne Skoll
ffba7fcb03 Make queue tests work at any time of the day.
All checks were successful
Remind unit tests / tests (push) Successful in 33s
If the REMIND_RUNNING_TEST environment variable is set to 1, then
SystemTime adjusts times near midnight so the queue tests pass.

Remind prints a warning if it is set so you don't accidentally
set it in normal use.
2025-01-20 10:15:19 -05:00
Dianne Skoll
b3f3cb9ce0 Add test for previous change
All checks were successful
Remind unit tests / tests (push) Successful in 31s
2025-01-19 22:05:25 -05:00
Dianne Skoll
6f11e727f8 Truncate any absurdly-long translations of "am" or "pm" rather than letting a buffer overflow. 2025-01-19 22:01:45 -05:00
Dianne Skoll
9f7ea96e87 Make it a bit easier to read
All checks were successful
Remind unit tests / tests (push) Successful in 41s
2025-01-18 10:45:35 -05:00
31 changed files with 1142 additions and 233 deletions

View File

@@ -6,11 +6,11 @@ the GNU General Public License, Vesion 2.
## Prerequisites:
`remind` and `rem2ps` have no prerequisites beyond the standard C library and
remind and rem2ps have no prerequisites beyond the standard C library and
the standard math library.
`rem2html` requires the `JSON::MaybeXS` Perl module and `rem2pdf`
requires the `JSON::MaybeXS`, `Pango` and `Cairo` Perl modules.
rem2html requires the JSON::MaybeXS Perl module and rem2pdf
requires the JSON::MaybeXS, Pango and Cairo Perl modules.
- On Debian-like systems, these prerequisites may be installed with:
@@ -24,7 +24,7 @@ requires the `JSON::MaybeXS`, `Pango` and `Cairo` Perl modules.
- On Arch linux, you need `pango-perl`, `cairo-perl` and `perl-json-maybexs`
TkRemind requires Tcl/Tk and the `tcllib` library.
TkRemind requires Tcl/Tk and the tcllib library.
- On Debian-like systems, install with:
@@ -49,9 +49,9 @@ Remind can be installed with the usual:
`./configure && make && make test && sudo make install`
You can edit `custom.h` to configure some aspects of Remind. Or, if
You can edit custom.h to configure some aspects of Remind. Or, if
you have Tcl/Tk installed, you can use the graphical build tool to
edit `custom.h` on your behalf:
edit custom.h on your behalf:
`wish ./build.tk`

18
configure vendored
View File

@@ -1,6 +1,6 @@
#! /bin/sh
# Guess values for system-dependent variables and create Makefiles.
# Generated by GNU Autoconf 2.71 for remind 05.02.03.
# Generated by GNU Autoconf 2.71 for remind 05.03.00.
#
#
# Copyright (C) 1992-1996, 1998-2017, 2020-2021 Free Software Foundation,
@@ -608,8 +608,8 @@ MAKEFLAGS=
# Identity of this package.
PACKAGE_NAME='remind'
PACKAGE_TARNAME='remind'
PACKAGE_VERSION='05.02.03'
PACKAGE_STRING='remind 05.02.03'
PACKAGE_VERSION='05.03.00'
PACKAGE_STRING='remind 05.03.00'
PACKAGE_BUGREPORT=''
PACKAGE_URL='https://dianne.skoll.ca/projects/remind/'
@@ -1265,7 +1265,7 @@ if test "$ac_init_help" = "long"; then
# Omit some internal or obsolete options to make the list less imposing.
# This message is too long to be a string in the A/UX 3.1 sh.
cat <<_ACEOF
\`configure' configures remind 05.02.03 to adapt to many kinds of systems.
\`configure' configures remind 05.03.00 to adapt to many kinds of systems.
Usage: $0 [OPTION]... [VAR=VALUE]...
@@ -1327,7 +1327,7 @@ fi
if test -n "$ac_init_help"; then
case $ac_init_help in
short | recursive ) echo "Configuration of remind 05.02.03:";;
short | recursive ) echo "Configuration of remind 05.03.00:";;
esac
cat <<\_ACEOF
@@ -1415,7 +1415,7 @@ fi
test -n "$ac_init_help" && exit $ac_status
if $ac_init_version; then
cat <<\_ACEOF
remind configure 05.02.03
remind configure 05.03.00
generated by GNU Autoconf 2.71
Copyright (C) 2021 Free Software Foundation, Inc.
@@ -1865,7 +1865,7 @@ cat >config.log <<_ACEOF
This file contains any messages produced by compilers while
running configure, to aid debugging if configure makes a mistake.
It was created by remind $as_me 05.02.03, which was
It was created by remind $as_me 05.03.00, which was
generated by GNU Autoconf 2.71. Invocation command line was
$ $0$ac_configure_args_raw
@@ -4710,7 +4710,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
# report actual input values of CONFIG_FILES etc. instead of their
# values after options handling.
ac_log="
This file was extended by remind $as_me 05.02.03, which was
This file was extended by remind $as_me 05.03.00, which was
generated by GNU Autoconf 2.71. Invocation command line was
CONFIG_FILES = $CONFIG_FILES
@@ -4775,7 +4775,7 @@ ac_cs_config_escaped=`printf "%s\n" "$ac_cs_config" | sed "s/^ //; s/'/'\\\\\\\\
cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
ac_cs_config='$ac_cs_config_escaped'
ac_cs_version="\\
remind config.status 05.02.03
remind config.status 05.03.00
configured by $0, generated by GNU Autoconf 2.71,
with options \\"\$ac_cs_config\\"

View File

@@ -1,6 +1,6 @@
dnl Process this file with autoconf to produce a configure script.
AC_INIT(remind, 05.02.03, , , https://dianne.skoll.ca/projects/remind/)
AC_INIT(remind, 05.03.00, , , https://dianne.skoll.ca/projects/remind/)
AC_CONFIG_SRCDIR([src/queue.c])
cat <<'EOF'

View File

@@ -112,7 +112,7 @@
(list "ADDOMIT" "AFTER" "AT" "BAN" "BANNER" "BEFORE" "CAL" "CLEAR"
"CLEAR-OMIT-CONTEXT" "DEBUG" "DO" "DUMP" "DUMPVARS" "DURATION" "ELSE"
"ENDIF" "ERRMSG" "EXIT" "EXPR" "FIRST" "FLUSH" "FOURTH" "FRENAME" "FROM" "FSET"
"FUNSET" "IF" "IFTRIG" "IN" "INC" "INCLUDE" "INCLUDECMD" "LAST"
"FUNSET" "IF" "IFTRIG" "IN" "INC" "INCLUDE" "INCLUDECMD" "INFO" "LAST"
"LASTDAY" "LASTWORKDAY" "MAYBE" "MAYBE-UNCOMPUTABLE" "MSF" "MSG"
"NOQUEUE" "OMIT" "OMITFUNC" "ONCE" "POP" "POP-OMIT-CONTEXT" "PRESERVE"
"PRIORITY" "PS" "PSFILE" "PUSH" "PUSH-OMIT-CONTEXT" "REM" "RUN"
@@ -166,7 +166,7 @@
(list "_" "abs" "access" "adawn" "adusk" "ampm" "ansicolor" "args" "asc"
"baseyr" "char" "choose" "coerce" "columns" "current" "date"
"datepart" "datetime" "dawn" "day" "daysinmon" "defined" "dosubst"
"dusk" "easterdate" "evaltrig" "filedate" "filedatetime" "filedir"
"dusk" "easterdate" "escape" "evaltrig" "filedate" "filedatetime" "filedir"
"filename" "getenv" "hebdate" "hebday" "hebmon" "hebyear" "hour"
"htmlescape" "htmlstriptags" "iif" "index" "isany" "isdst" "isleap"
"isomitted" "language" "localtoutc" "lower" "max" "min" "minsfromutc"

View File

@@ -1,6 +1,25 @@
CHANGES TO REMIND
* VERSION 5.2 Patch 3 - 2025-??-??
* VERSION 5.3 Patch 0 - 2025-02-??
- NEW FEATURE: remind: Add the "INFO" clause to the REM command. This
is intended for storing additional metadata about an event, such as
the location and a longer description. The intention is to make
Remind <-> iCal conversions preserve as much information as possible.
- NEW FEATURE: TkRemind: Add "Location" and "Description" fields when
creating a reminder; these are converted to INFO clauses. Also support
a popup window with the extra information when hovering over a reminder
in the calendar display.
- IMPROVEMENT: remind: Add the "\xAB" escape sequence for parsing quoted
strings, where "AB" is a pair of hex digits.
- NEW FUNCTION: remind: Add the escape() built-in function that converts
problematic characters within a string to the \-escaped versions.
It's essentially the inverse of how Remind parses a quoted string.
* VERSION 5.2 Patch 3 - 2025-01-22
- NEW FEATURE: remind: Add "TRANSLATE GENERATE" command for generating
a skeleton set of TRANSLATE commands to make it easier to localize
@@ -20,7 +39,10 @@ CHANGES TO REMIND
unknown debug flag.
- BUG FIX: remind: "make test" will now succeed even if run between
23:55 and 00:00 UTC.
23:55 and 00:00 UTC. This is done with a new --test flag for remind.
- BUG FIX: remind: Avoid potential buffer overflow if someone supplies
ridiculously-long translations for "am" or "pm".
* VERSION 5.2 Patch 2 - 2025-01-06

View File

@@ -516,6 +516,29 @@ MOON, etc.)
If any TAG clauses are present, the \fBtags\fR key will be present and consist
of a comma-separated list of tags.
.TP
.B info \fR{ \fIhash\fR }
If any INFO clauses are present, the \fBinfo\fR key will be present. Its
value will be a hash of info key-value pairs. Each key is the header
from an INFO string, \fIconverted to all lower-case\fR. The value is the
value from the INFO string.
.RS
.PP
For example, the following REM command:
.PP
.nf
REM INFO "Location: Boardroom" INFO "Summary: None" MSG whatever
.fi
.PP
will produce the following \fBinfo\fR hash:
.PP
.nf
"info" : {
"location" : "Boardroom",
"summary" : "None"
},
.fi
.RE
.TP
.B time \fIt\fR
If an AT clause was present, this key will contain the time of the AT clause
in minutes after midnight.

View File

@@ -530,11 +530,16 @@ resources. Note that the limit \fIn\fR is approximate and
\fBRemind\fR might execute for one or two more seconds before it is
killed. If \fIn\fR is specified as zero, then no limit is applied, just
as if the option had not been used at all.
.RS
.PP
If a limit is applied, it applies only to the foreground run of \fBRemind\fR.
If \fBRemind\fR finishes processing the script and then starts handling
queued reminders, the time limit is reset to no limit.
.PP
.RE
.TP
.B \-\-test
The \fB\-\-test\fR long option is only for use by the acceptance tests
run by "make test". Do not use this option in production.
.SH REMINDER FILES
.PP
\fBRemind\fR uses scripts to control its operation. You can use any
@@ -601,8 +606,9 @@ Its syntax is:
[\fBSCANFROM\fR \fIscan_date\fR | \fBFROM\fR \fIstart_date\fR]
[\fBDURATION\fR \fIduration\fR]
[\fBTAG\fR \fItag\fR]
<\fBMSG\fR | \fBMSF\fR | \fBRUN\fR | \fBCAL\fR | \fBSATISFY\fR |
\fBSPECIAL\fR \fIspecial\fR | \fBPS\fR | \fBPSFILE\fR>
[\fBINFO\fR "\fIinfo_string\fR"]
\fBMSG\fR | \fBMSF\fR | \fBRUN\fR | \fBCAL\fR | \fBSATISFY\fR |
\fBSPECIAL\fR \fIspecial\fR | \fBPS\fR | \fBPSFILE\fR
.I body
.RE
.PP
@@ -1338,7 +1344,7 @@ However, discussion must be deferred until after
expressions and user-defined functions are explained. See the subsection
"PRECISE SCHEDULING" further on.
.PP
.B TAG AND DURATION
.B TAG, INFO AND DURATION
.PP
The \fBTAG\fR keyword lets you "tag" certain reminders. This facility
is used by certain back-ends or systems built around \fBRemind\fR,
@@ -1357,15 +1363,40 @@ by the hexadecimal representation of the MD5 sum of the REM
command line. This lets you give a more-or-less unique identifier
to each distinct REM command.
.PP
The \fBINFO\fR keyword is similar to \fBTAG\fR but is intended to convey
metadata about an event, such as its location. Back-ends will have their
own rules about which \fIinfo_string\fRs they recognize, and must ignore
\fIinfo_string\fRs they don't recognize. Note that \fBINFO\fR must
be followed by a quoted string; you can include newlines in the string
by supplying them as "\\n".
.PP
An INFO string \fImust\fR be of the form "Header: Value". The header
can consist of any printable character, but cannot contain whitespace.
The value can consist of any characters you like. Space may not
appear before the colon, but can appear afterwards; such space is not
considered to be part of the value. If there is more than one INFO
string for a given reminder, there cannot be any duplicate headers.
Case is ignored when determining if a header is a duplicate of an
existing one.
.PP
For example, a hypothetical back-end might let you set the location
and description of a reminder like this:
.PP
.nf
REM 26 Feb 2025 INFO "Location: Boardroom #2" \\
INFO "Description: Go over latest pull requests" \\
MSG Engineering meeting
.fi
.PP
The \fBDURATION\fR keyword makes sense only for timed reminders; it
specifies the duration of an event. For example, if you have a
90-minute meeting starting at 1:00pm, you could use any of the following:
.PP
.nf
REM 5 March 2021 AT 13:00 DURATION 1:30 MSG Meeting
REM 5 March 2021 AT 13:00 DURATION 90 MSG Meeting
REM 5 March 2021 AT 1:00pm DURATION 1:30 MSG Meeting
REM 5 March 2021 AT 1:00pm DURATION 90 MSG Meeting
REM 5 March 2021 AT 13:00 DURATION 1:30 MSG Meeting
REM 5 March 2021 AT 13:00 DURATION 90 MSG Meeting
REM 5 March 2021 AT 1:00pm DURATION 1:30 MSG Meeting
REM 5 March 2021 AT 1:00pm DURATION 90 MSG Meeting
.fi
.PP
For long-duration reminders, it is convenient to use expressions
@@ -1395,26 +1426,26 @@ The REM command has syntactic sugar to let you express common
reminders. The following pairs of reminders are equivalent:
.PP
.nf
REM First Monday April MSG Foo
REM Mon 1 April MSG Foo
REM First Monday April MSG Foo
REM Mon 1 April MSG Foo
REM Second Monday May MSG Bar
REM Mon 8 May MSG Bar
REM Second Monday May MSG Bar
REM Mon 8 May MSG Bar
REM Third Monday MSG Third Monday of every month
REM Mon 15 MSG Third Monday of every month
REM Third Monday MSG Third Monday of every month
REM Mon 15 MSG Third Monday of every month
REM Fourth Sunday June 2025 MSG Fourth Sunday in June 2025
REM Sun 22 June 2025 MSG Fourth Sunday in June 2025
REM Fourth Sunday June 2025 MSG Fourth Sunday in June 2025
REM Sun 22 June 2025 MSG Fourth Sunday in June 2025
REM Last Monday MSG Last Monday of every month
REM Mon 1 --7 MSG Last Monday of every month
REM Last Monday MSG Last Monday of every month
REM Mon 1 --7 MSG Last Monday of every month
REM Last Monday April MSG Last Monday of every April
REM Mon 1 May --7 MSG Last Monday of every April
REM Last Monday April MSG Last Monday of every April
REM Mon 1 May --7 MSG Last Monday of every April
REM Last Monday December 2025 MSG Last Monday of Dec 2025
REM Monday 1 Jan 2026 --7 MSG Last Monday of Dec 2025
REM Last Monday December 2025 MSG Last Monday of Dec 2025
REM Monday 1 Jan 2026 --7 MSG Last Monday of Dec 2025
.fi
.PP
Note that \fBLast\fR effectively adjusts the month and year, if necessary, to
@@ -1423,35 +1454,35 @@ make the reminder trigger on the correct date.
The keyword \fBIN\fR is completely ignored, so you can write (for example):
.PP
.nf
REM Second Monday in May MSG foo
REM Last Monday in December 2025 MSG Bar
REM Second Monday in May MSG foo
REM Last Monday in December 2025 MSG Bar
.fi
.PP
An alternate form of \fIback\fR makes writing reminders easier.
The following groups of reminders are equivalent:
.PP
.nf
REM ~~1 MSG Last day of every month
REM Lastday MSG Last day of every month
REM 1 --1 MSG Last day of every month
REM ~~1 MSG Last day of every month
REM Lastday MSG Last day of every month
REM 1 --1 MSG Last day of every month
REM May ~~1 MSG Last day of May
REM Lastday May MSG Last day of May
REM 1 June --1 MSG Last day of May
REM May ~~1 MSG Last day of May
REM Lastday May MSG Last day of May
REM 1 June --1 MSG Last day of May
REM Dec 2025 ~~1 MSG Last day of December 2025
REM Lastday Dec 2025 MSG Last day of December 2025
REM 1 Jan 2026 --1 MSG Last day of December 2025
REM Dec 2025 ~~1 MSG Last day of December 2025
REM Lastday Dec 2025 MSG Last day of December 2025
REM 1 Jan 2026 --1 MSG Last day of December 2025
REM Apr ~1 OMIT SAT SUN MSG Last workday of April
REM Lastworkday April OMIT SAT SUN MSG Last workday of April
REM 1 May -1 OMIT SAT SUN MSG Last workday of April
REM Apr ~1 OMIT SAT SUN MSG Last workday of April
REM Lastworkday April OMIT SAT SUN MSG Last workday of April
REM 1 May -1 OMIT SAT SUN MSG Last workday of April
REM Apr ~~7 MSG Seventh-last day of April
REM 1 May --7 MSG Seventh-last day of April
REM Apr ~~7 MSG Seventh-last day of April
REM 1 May --7 MSG Seventh-last day of April
REM Apr ~2 OMIT SAT SUN MSG Second-last workday of April
REM 1 May -2 OMIT SAT SUN MSG Second-last workday of April
REM Apr ~2 OMIT SAT SUN MSG Second-last workday of April
REM 1 May -2 OMIT SAT SUN MSG Second-last workday of April
.fi
.PP
As we see, "Lastday" is equivalent to ~~1 and "Lastworkday" to ~1.
@@ -1462,9 +1493,9 @@ be combined with a day. Additionally, First/Second/Third/Fourth/Last
must have at least one weekday name. The following are illegal:
.PP
.nf
REM First Monday 3 June MSG Huh?
REM April 3 ~~1 MSG What?
REM Second June MSG Where's the weekday???
REM First Monday 3 June MSG Huh?
REM April 3 ~~1 MSG What?
REM Second June MSG Where's the weekday???
.fi
.PP
.SH THE SUBSTITUTION FILTER
@@ -2223,6 +2254,10 @@ The following examples illustrate constants in \fBRemind\fR expressions:
Note that the empty string is represented by "". Remind supports
the escape sequences "\\a", "\\b", "\\f", "\\n", "\\r", "\\t"
and "\\v" which have the same meanings as their counterparts in C.
It also supports "\\xAB" where A and B are hexadecimal digits;
this operates just as in C. The "\\x" must be followed by one or
two hex digits; the escape sequence "\\x00" is not permitted.
.PP
To include a quote in a string, use "\\"". Any other character
preceded by a backslash is inserted into the string as-is, but the
backslash itself is removed. To include a backslash in a string,
@@ -3391,6 +3426,25 @@ Note that \fBeasterdate\fR computes the Western Easter. For the Orthodox
Easter date, see \fBorthodoxeaster\fR.
.RE
.TP
.B escape(s_string [,i_add_quotes])
Returns a \fBSTRING\fR that is the same as the input string, but with
all special characters backslashed-escaped. For example, the following
command:
.RS
.PP
.nf
set a escape("foo" + char(10) + "bar")
.fi
.PP
will set a to "foo\\nbar" where "\\n" is the literal sequence "\\" followed
by "n". This is useful if you want to compute a string in a pasted-in
expression that \fBRemind\fR will then parse as a quoted string again,
such as in a TRANSLATE command or an INFO clause.
.PP
If the optional \fIadd_quotes\fR argument is supplied and is non-zero, then
the return value from \fBescape\fR will include surrounding double-quotes.
.RE
.TP
.B evaltrig(s_trigger [,dq_start])
Evaluates \fItrigger\fR as if it were a REM or IFTRIG trigger specification
and returns the trigger date as a \fBDATE\fR (or as a \fBDATETIME\fR if
@@ -3685,18 +3739,18 @@ In general, \fBmultitrig\fR works better with the Remind algorithm than
reminder is issued at the end of each quarter:
.PP
.nf
REM [multitrig("Mar 31", "Jun 30", "Sep 30", "Dec 31")] +7 MSG \\
%"End of [ord($Tm/3)] quarter%" is %b.
REM [multitrig("Mar 31", "Jun 30", "Sep 30", "Dec 31")] +7 MSG \\
%"End of [ord($Tm/3)] quarter%" is %b.
.fi
.PP
If you want the last working day of each quarter, you could use:
.PP
.nf
PUSH-OMIT-CONTEXT
OMIT Sat Sun
REM [multitrig("Mar ~1", "Jun ~1", "Sep ~1", "Dec ~1")] +7 MSG \\
%"Last working day of [ord($Tm/3)] quarter%" is %b.
POP-OMIT-CONTEXT
PUSH-OMIT-CONTEXT
OMIT Sat Sun
REM [multitrig("Mar ~1", "Jun ~1", "Sep ~1", "Dec ~1")] +7 MSG \\
%"Last working day of [ord($Tm/3)] quarter%" is %b.
POP-OMIT-CONTEXT
.fi
.PP
Note that unlike \fBevaltrig\fR, \fBmultitrig\fR always returns a \fBDATE\fR

View File

@@ -102,7 +102,10 @@ You can also specify advance notice, possibly repeating.
The sixth control specifies what \fBRemind\fR should do if a reminder
falls on a holiday or weekend.
Enter the body of the reminder into the \fBBody:\fR text entry.
Enter the body of the reminder into the \fBSummary:\fR text entry. If
you want, you can enter a location and longer description in the
\fBLocation:\fR and \fBDescription:\fR boxes. If you enter anything
here, they will be added as \fBINFO\fR items to the reminder.
To add the reminder to the reminder file, click \fBAdd to reminder file\fR.
To close the dialog without adding the reminder to the file, click
@@ -178,6 +181,10 @@ is editable with an editor but was not created using \fBTkRemind\fR,
it will be underlined when you move the cursor over it, and
you can edit it in a text editor by either left- or right-clicking
on the reminder.
.PP
If a reminder has location or description \fBINFO\fR items, then
hovering over the reminder will pop up a window containing the
location and/or description information.
.SH ERRORS

View File

@@ -280,6 +280,9 @@ set AppendFile $ReminderFile
# Array of tags -> JSON dicts
array unset TagToObj
# Array of __syn__ tags -> JSON dicts
array unset SynToObj
set SetFontsWorked 0
#---------------- DON'T CHANGE STUFF BELOW HERE ------------------
@@ -1138,9 +1141,10 @@ proc ConfigureCalWindow { month year firstDay numDays } {
proc FillCalWindow {} {
set FileName ""
set LineNo 0
global DayNames CurYear CurMonth MonthNames CommandLine Option TagToObj RemindErrors MondayFirst
global DayNames CurYear CurMonth MonthNames CommandLine Option TagToObj SynToObj RemindErrors MondayFirst
array unset TagToObj
array unset SynToObj
Status "Firing off Remind..."
set_button_to_queue
@@ -1230,7 +1234,7 @@ proc FillCalWindow {} {
set day [string trimleft $day 0]
set n [expr $day+$offset]
set month [string trimleft $month 0]
set extratags ""
set extratags {}
switch -nocase -- $type {
"WEEK" {
set stuff [string trimleft $stuff]
@@ -1269,9 +1273,9 @@ proc FillCalWindow {} {
set b 0
}
set color [format "%02X%02X%02X" $r $g $b]
set extratags "clr$color"
lappend extratags "clr$color"
.cal.t$n configure -state normal
.cal.t$n tag configure $extratags -foreground "#$color"
.cal.t$n tag configure "clr$color" -foreground "#$color"
.cal.t$n configure -state disabled -takefocus 0
set stuff $stuff
set type "COLOR"
@@ -1288,18 +1292,27 @@ proc FillCalWindow {} {
set stuff [regsub -all {\n[ \t]} $stuff "\n"]
set stuff [regsub -all {\n+} $stuff "\n"]
if {[regexp {__syn__([0-9a-f]+)} $tag syntag]} {
set SynToObj($syntag) $obj
lappend extratags $syntag
.cal.t$n tag bind $syntag <Enter> [list details_enter .cal.t$n]
.cal.t$n tag bind $syntag <Leave> [list details_leave .cal.t$n]
} else {
set syntag ""
}
if {[regexp {TKTAG([0-9]+)} $tag all tagno] && "$fntag" != "x"} {
.cal.t$n insert end [string trim $stuff] [list REM TAGGED "TKTAG$tagno" "date_$date" $extratags $fntag]
.cal.t$n tag bind "TKTAG$tagno" <Enter> "TaggedEnter .cal.t$n"
.cal.t$n tag bind "TKTAG$tagno" <Leave> "TaggedLeave .cal.t$n"
set TagToObj(TKTAG$tagno) $obj
.cal.t$n insert end [string trim $stuff] [concat REM TAGGED "TKTAG$tagno" "date_$date" $extratags $fntag]
.cal.t$n tag bind "TKTAG$tagno" <Enter> [list TaggedEnter .cal.t$n]
.cal.t$n tag bind "TKTAG$tagno" <Leave> [list TaggedLeave .cal.t$n]
set TagToObj($all) $obj
} else {
if {"$fntag" == "x" } {
.cal.t$n insert end [string trim $stuff] [list REM $extratags]
.cal.t$n insert end [string trim $stuff] [concat REM $extratags]
} else {
.cal.t$n insert end [string trim $stuff] [list REM $extratags $fntag]
.cal.t$n tag bind $fntag <Enter> "EditableEnter .cal.t$n"
.cal.t$n tag bind $fntag <Leave> "EditableLeave .cal.t$n"
.cal.t$n insert end [string trim $stuff] [concat REM $extratags $fntag]
.cal.t$n tag bind $fntag <Enter> [list EditableEnter .cal.t$n]
.cal.t$n tag bind $fntag <Leave> [list EditableLeave .cal.t$n]
.cal.t$n tag bind $fntag <ButtonPress-1> "FireEditor .cal.t$n"
}
}
@@ -1707,8 +1720,9 @@ proc CreateModifyDialog {w day firstDay args} {
frame $w.msg
frame $w.buttons
pack $w.o1 $w.o2 $w.o3 -side top -anchor w -in $w.o
pack $w.o $w.exp $w.adv $w.weekend $w.time $w.durationbox $w.hol $w.msg -side top -anchor w -pady 4 -expand 1 -fill both
pack $w.buttons -side top -anchor w -pady 4 -expand 1 -fill x
pack $w.o $w.exp $w.adv $w.weekend $w.time $w.durationbox $w.hol $w.msg -side top -anchor w -pady 4 -expand 0 -fill both
pack $w.msg -side top -anchor w -pady 4 -padx 4 -expand true -fill both
pack $w.buttons -side top -anchor w -pady 4 -expand 0 -fill x
# TYPE 1 REMINDER
radiobutton $w.type1 -variable OptionType -value 1
@@ -1905,12 +1919,30 @@ proc CreateModifyDialog {w day firstDay args} {
pack $w.labhol $w.issue $w.skip $w.before $w.after -side top -anchor w -in $w.hol
# TEXT ENTRY
label $w.msglab -text "Body:"
label $w.msglab -text "Summary: "
entry $w.entry
balloon_add_help $w.entry "Enter the text of the reminder"
pack $w.msglab -side left -anchor w -in $w.msg
pack $w.entry -side left -anchor w -expand 1 -fill x -in $w.msg
grid $w.msglab -row 0 -column 0 -in $w.msg -sticky e
grid $w.entry -row 0 -column 1 -in $w.msg -sticky nsew
# LOCATION and DESCRIPTION
label $w.loclab -text "Location: "
entry $w.location
balloon_add_help $w.location "Enter the location, if any"
grid $w.loclab -row 1 -column 0 -in $w.msg -sticky e
grid $w.location -row 1 -column 1 -in $w.msg -sticky nsew
label $w.desclab -text "Description: "
text $w.description -width 80 -height 5
balloon_add_help $w.description "Enter a detailed description, if any"
grid $w.desclab -row 2 -column 0 -in $w.msg -sticky e
grid $w.description -row 2 -column 1 -in $w.msg -sticky nsew
grid columnconfigure $w.msg 0 -weight 0
grid columnconfigure $w.msg 1 -weight 1
grid rowconfigure $w.msg 0 -weight 0
grid rowconfigure $w.msg 1 -weight 0
grid rowconfigure $w.msg 2 -weight 1
# BUTTONS
set nbut 0
foreach but $args {
@@ -1988,6 +2020,11 @@ proc OptionsToRemindDialog { w opts } {
set hour $value
}
}
"-txtentry-*" {
set win [string range $flag 10 end]
$w.$win delete 1.0 end
$w.$win insert end $value
}
"-global-*" {
set win [string range $flag 8 end]
set $win $value
@@ -2170,6 +2207,13 @@ proc CenterWindow {w {parent {}}} {
wm deiconify $w
}
#---------------------------------------------------------------------------
# RemQuotedString - return a quoted string with difficult characters escaped
#---------------------------------------------------------------------------
proc RemQuotedString { str } {
set str [string map {"\n" "\\n" "\"" "\\\"" "[" "[\"[\"]"} $str]
return "\"$str\""
}
#---------------------------------------------------------------------------
# CreateReminder -- create the reminder
# Arguments:
@@ -2255,7 +2299,16 @@ proc CreateReminder {w} {
append rem " OMIT [GetWeekend $w 1]"
}
set location [string trim [$w.location get]]
if {$location != ""} {
set location "Location: $location"
append rem " INFO [RemQuotedString $location]"
}
set description [string trim [$w.description get 1.0 end]]
if {$description != ""} {
set description "Description: $description"
append rem " INFO [RemQuotedString $description]"
}
# Check it out!
global Remind
set f [open "|$Remind -arq -e - 2>@1" r+]
@@ -3443,6 +3496,16 @@ proc ReadTaggedOptions { tag date } {
lappend ans -text-year3 $y
}
}
if {[dict exists $obj info]} {
set info [dict get $obj info]
if {[dict exists $info location]} {
lappend ans -entry-location [dict get $info location]
}
if {[dict exists $info description]} {
lappend ans -txtentry-description [dict get $info description]
}
}
return $ans
}
@@ -3573,6 +3636,87 @@ proc EditableLeave { w } {
set tag [lindex $tags $index]
$w tag configure $tag -underline 0
}
proc details_enter { w } {
global SynToObj Balloon
set tags [$w tag names current]
set index [lsearch -glob $tags "__syn__*"]
if {$index < 0} {
return
}
set syntag [lindex $tags $index]
if {![info exists SynToObj($syntag)]} {
return
}
set obj $SynToObj($syntag)
set lines {}
if {![dict exists $obj info]} {
return;
}
set info [dict get $obj info]
set llen 0
if {[dict exists $info location]} {
lappend lines [list "Location:" [dict get $info location]]
}
if {[dict exists $info description]} {
lappend lines [list "Description:" [dict get $info description]]
}
if {[llength $lines] < 1} {
return;
}
balloon_cancel_timer
set Balloon(HelpId) [after $Balloon(HelpTime) [list details_popup $lines]]
}
proc details_leave { w } {
balloon_cancel_timer
catch { destroy .balloonhelp }
}
proc details_popup { pairs } {
global Balloon
set maxwid 80
set h .balloonhelp
toplevel $h -bg #000000
text $h.l -width $maxwid -height 16 -bd 0 -wrap word -relief flat -bg #FFFFC0
$h.l tag configure bold -font {-weight bold}
$h.l tag configure medium -font {-weight normal}
foreach pair $pairs {
$h.l insert end [lindex $pair 0] bold " " medium [lindex $pair 1] medium "\n" medium
}
# Now calculate actual text window size
set wid 0
set height 0
set text [$h.l get 1.0 end]
set text [string trim $text]
set lines [split $text "\n"]
foreach line $lines {
incr height
set len [string length $line]
incr len
if {$len > $wid} {
set wid $len
}
}
if {$wid > $maxwid} {
set wid $maxwid
}
### NOTE: I should be using "count -displaylines" to size
### the window, but Tk gives the wrong answer. I think
### there is a bug in the text widget. So we count the
### number of lines and add 5 and hope for the best...
incr height 5
$h.l configure -width $wid -height $height
pack $h.l -padx 1 -pady 1 -ipadx 2 -ipady 1
$h.l configure -state disabled
wm overrideredirect $h 1
set geom [balloon_calculate_geometry $h]
wm geometry $h $geom
set Balloon(HelpId) [after $Balloon(StayTime) [list catch { destroy $h }]]
set Balloon(MustLeave) 1
}
#***********************************************************************
# %PROCEDURE: EditTaggedReminder
# %ARGUMENTS:

View File

@@ -61,6 +61,7 @@ typedef struct cal_entry {
TimeTrig tt;
int nonconst_expr;
int if_depth;
TrigInfo *infos;
} CalEntry;
/* Line-drawing sequences */
@@ -387,7 +388,13 @@ void PrintJSONChar(char c) {
case '\t': printf("\\t"); break;
case '"': printf("\\\""); break;
case '\\': printf("\\\\"); break;
default: printf("%c", c);
default:
if ((c > 0 && c < 32) || c == 0x7f) {
printf("\\u%04x", (unsigned int) c);
} else {
printf("%c", c);
}
break;
}
}
@@ -402,7 +409,36 @@ void PrintJSONString(char const *s)
case '\t': printf("\\t"); break;
case '"': printf("\\\""); break;
case '\\': printf("\\\\"); break;
default: printf("%c", *s);
default:
if ((*s > 0 && *s < 32) || *s == 0x7f) {
printf("\\u%04x", (unsigned int) *s);
} else {
printf("%c", *s);
}
break;
}
s++;
}
}
void PrintJSONStringLC(char const *s)
{
while (*s) {
switch(*s) {
case '\b': printf("\\b"); break;
case '\f': printf("\\f"); break;
case '\n': printf("\\n"); break;
case '\r': printf("\\r"); break;
case '\t': printf("\\t"); break;
case '"': printf("\\\""); break;
case '\\': printf("\\\\"); break;
default:
if ((*s > 0 && *s < 32) || *s == 0x7f) {
printf("\\u%04x", (unsigned int) *s);
} else {
printf("%c", tolower(*s));
}
break;
}
s++;
}
@@ -499,7 +535,7 @@ get_month_abbrev(char const *mon)
{
static char buf[80];
#ifndef REM_USE_WCHAR
sprintf(buf, "%.3s", mon);
snprintf(buf, sizeof(buf), "%.3s", mon);
return buf;
#else
char *s;
@@ -626,9 +662,9 @@ Colorize256(int r, int g, int b, int bg, int clamp)
}
}
if (bg) {
sprintf(buf, "\x1B[48;5;%dm", best);
snprintf(buf, sizeof(buf), "\x1B[48;5;%dm", best);
} else {
sprintf(buf, "\x1B[38;5;%dm", best);
snprintf(buf, sizeof(buf), "\x1B[38;5;%dm", best);
}
return buf;
}
@@ -641,9 +677,9 @@ ColorizeTrue(int r, int g, int b, int bg, int clamp)
ClampColor(&r, &g, &b);
}
if (bg) {
sprintf(buf, "\x1B[48;2;%d;%d;%dm", r, g, b);
snprintf(buf, sizeof(buf), "\x1B[48;2;%d;%d;%dm", r, g, b);
} else {
sprintf(buf, "\x1B[38;2;%d;%d;%dm", r, g, b);
snprintf(buf, sizeof(buf), "\x1B[38;2;%d;%d;%dm", r, g, b);
}
return buf;
}
@@ -1504,6 +1540,7 @@ static int WriteOneColLine(int col)
free(e->raw_text);
free(e->filename);
if (e->wc_text) free(e->wc_text);
FreeTrigInfoChain(e->infos);
free(e);
return 1;
}
@@ -1590,6 +1627,7 @@ static int WriteOneColLine(int col)
free(e->raw_text);
free(e->filename);
if (e->wc_text) free(e->wc_text);
FreeTrigInfoChain(e->infos);
free(e);
} else {
e->wc_pos = ws;
@@ -1611,6 +1649,7 @@ static int WriteOneColLine(int col)
if (e->wc_text) free(e->wc_text);
#endif
free(e->raw_text);
FreeTrigInfoChain(e->infos);
free(e);
return 1;
}
@@ -1673,6 +1712,7 @@ static int WriteOneColLine(int col)
if (e->wc_text) free(e->wc_text);
#endif
free(e->raw_text);
FreeTrigInfoChain(e->infos);
free(e);
} else {
e->pos = s;
@@ -1812,7 +1852,7 @@ static void WriteCalHeader(void)
int y, m, d;
FromDSE(DSEToday, &y, &m, &d);
sprintf(buf, "%s %d", get_month_name(m), y);
snprintf(buf, sizeof(buf), "%s %d", get_month_name(m), y);
WriteTopCalLine();
@@ -2016,7 +2056,7 @@ static int DoCalRem(ParsePtr p, int col)
trig.typ == MSF_TYPE) {
if (PsCal && is_color) {
char cbuf[24];
sprintf(cbuf, "%d %d %d ", col_r, col_g, col_b);
snprintf(cbuf, sizeof(cbuf), "%d %d %d ", col_r, col_g, col_b);
DBufPuts(&pre_buf, cbuf);
strcpy(trig.passthru, "COLOR");
/* Don't change trig.typ or next if() will trigger! */
@@ -2149,7 +2189,7 @@ static int DoCalRem(ParsePtr p, int col)
if (trig.typ != PASSTHRU_TYPE &&
UserFuncExists("calprefix")==1) {
char evalBuf[64];
sprintf(evalBuf, "calprefix(%d)", trig.priority);
snprintf(evalBuf, sizeof(evalBuf), "calprefix(%d)", trig.priority);
s2 = evalBuf;
r = EvalExpr(&s2, &v, NULL);
if (!r) {
@@ -2192,7 +2232,7 @@ static int DoCalRem(ParsePtr p, int col)
if (trig.typ != PASSTHRU_TYPE &&
UserFuncExists("calsuffix")==1) {
char evalBuf[64];
sprintf(evalBuf, "calsuffix(%d)", trig.priority);
snprintf(evalBuf, sizeof(evalBuf), "calsuffix(%d)", trig.priority);
s2 = evalBuf;
r = EvalExpr(&s2, &v, NULL);
if (!r) {
@@ -2232,6 +2272,7 @@ static int DoCalRem(ParsePtr p, int col)
FreeTrig(&trig);
return E_NO_MEM;
}
e->infos = NULL;
e->nonconst_expr = nonconst_expr;
e->if_depth = NumIfs;
e->trig = trig;
@@ -2252,6 +2293,7 @@ static int DoCalRem(ParsePtr p, int col)
if (!e->text || !e->raw_text) {
if (e->text) free(e->text);
if (e->raw_text) free(e->raw_text);
FreeTrigInfoChain(e->infos);
free(e);
FreeTrig(&trig);
return E_NO_MEM;
@@ -2265,12 +2307,17 @@ static int DoCalRem(ParsePtr p, int col)
AppendTag(&(e->tags), SynthesizeTag());
}
/* Take over any TrigInfo! */
e->infos = trig.infos;
trig.infos = NULL;
/* Don't need tags any more */
FreeTrig(&trig);
e->duration = tim.duration;
e->priority = trig.priority;
e->filename = StrDup(FileName);
if(!e->filename) {
FreeTrigInfoChain(e->infos);
if (e->text) free(e->text);
if (e->raw_text) free(e->raw_text);
#ifdef REM_USE_WCHAR
@@ -2342,6 +2389,41 @@ void WriteJSONTimeTrigger(TimeTrig const *tt)
}
}
static void
WriteJSONInfoChain(TrigInfo *ti)
{
printf("\"info\":{");
while (ti) {
/* Skanky... */
char *colon = (char *) strchr(ti->info, ':');
char const *value;
if (!colon) {
/* Should be impossible... */
ti = ti->next;
continue;
}
/* Terminate the string at the colon */
*colon = 0;
value = colon+1;
while(*value && isspace(*value)) {
value++;
}
printf("\"");
PrintJSONStringLC(ti->info);
printf("\":\"");
PrintJSONString(value);
printf("\"");
/* Restore the value of the colon */
*colon = ':';
if (ti->next) {
printf(",");
}
ti = ti->next;
}
printf("},");
}
void WriteJSONTrigger(Trigger const *t, int include_tags, int today)
{
/* wd is an array of days from 0=monday to 6=sunday.
@@ -2433,6 +2515,9 @@ void WriteJSONTrigger(Trigger const *t, int include_tags, int today)
PrintJSONKeyPairInt("addomit", 1);
}
if (include_tags) {
if (t->infos) {
WriteJSONInfoChain(t->infos);
}
PrintJSONKeyPairString("tags", DBufValue(&(t->tags)));
}
}
@@ -2446,6 +2531,9 @@ static void WriteSimpleEntryProtocol2(CalEntry *e, int today)
}
PrintJSONKeyPairString("passthru", e->passthru);
PrintJSONKeyPairString("tags", DBufValue(&(e->tags)));
if (e->infos) {
WriteJSONInfoChain(e->infos);
}
if (e->duration != NO_TIME) {
PrintJSONKeyPairInt("duration", e->duration);
}
@@ -2576,6 +2664,7 @@ static void WriteSimpleEntries(int col, int dse)
free(e->text);
free(e->raw_text);
free(e->filename);
FreeTrigInfoChain(e->infos);
#ifdef REM_USE_WCHAR
if (e->wc_text) free(e->wc_text);
#endif
@@ -2736,7 +2825,7 @@ CalendarTime(int tim, int duration)
else hh2 = h2;
if (days) {
sprintf(daybuf, "+%d", days);
snprintf(daybuf, sizeof(daybuf), "+%d", days);
} else {
daybuf[0] = 0;
}
@@ -2759,12 +2848,12 @@ CalendarTime(int tim, int duration)
switch(ScFormat) {
case SC_AMPM:
sprintf(buf, "%d%c%02d%s-%d%c%02d%s%s ",
snprintf(buf, sizeof(buf), "%d%c%02d%s-%d%c%02d%s%s ",
hh, TimeSep, min, ampm1, hh2, TimeSep, min2, ampm2, daybuf);
break;
case SC_MIL:
sprintf(buf, "%02d%c%02d-%02d%c%02d%s ",
snprintf(buf, sizeof(buf), "%02d%c%02d-%02d%c%02d%s ",
h, TimeSep, min, h2, TimeSep, min2, daybuf);
break;
}
@@ -2782,7 +2871,7 @@ CalendarTime(int tim, int duration)
/***************************************************************/
char const *SimpleTime(int tim)
{
static char buf[32];
static char buf[128];
int h, min, hh;
buf[0] = 0;
@@ -2796,7 +2885,7 @@ char const *SimpleTime(int tim)
if (h == 0) hh=12;
else if (h > 12) hh=h-12;
else hh=h;
sprintf(buf, "%d%c%02d%s ", hh, TimeSep, min, (h>=12) ? tr("pm") : tr("am"));
snprintf(buf, sizeof(buf), "%d%c%02d%.64s ", hh, TimeSep, min, (h>=12) ? tr("pm") : tr("am"));
}
break;
@@ -2804,7 +2893,7 @@ char const *SimpleTime(int tim)
if (tim != NO_TIME) {
h = tim / 60;
min = tim % 60;
sprintf(buf, "%02d%c%02d ", h, TimeSep, min);
snprintf(buf, sizeof(buf), "%02d%c%02d ", h, TimeSep, min);
}
break;
}
@@ -2855,8 +2944,11 @@ char const *SynthesizeTag(void)
static char out[128];
MD5Init(&ctx);
MD5Update(&ctx, (unsigned char *) CurLine, strlen(CurLine));
MD5Update(&ctx, (unsigned char *) FileName, strlen(FileName));
snprintf((char *) buf, sizeof(buf), "%d", LineNo);
MD5Update(&ctx, buf, strlen( (char *) buf));
MD5Final(buf, &ctx);
sprintf(out, "__syn__%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
snprintf(out, sizeof(out), "__syn__%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
(unsigned int) buf[0], (unsigned int) buf[1],
(unsigned int) buf[2], (unsigned int) buf[3],
(unsigned int) buf[4], (unsigned int) buf[5],

View File

@@ -116,4 +116,4 @@
/* Define to 1 if your <sys/time.h> declares `struct tm'. */
#undef TM_IN_SYS_TIME
#include <custom.h>
#include "custom.h"

View File

@@ -408,6 +408,7 @@ int ParseRem(ParsePtr s, Trigger *trig, TimeTrig *tim)
tim->duration = NO_TIME;
trig->need_wkday = 0;
trig->adj_for_last = 0;
trig->infos = NULL;
int parsing = 1;
while(parsing) {
@@ -649,6 +650,15 @@ int ParseRem(ParsePtr s, Trigger *trig, TimeTrig *tim)
DBufFree(&buf);
break;
case T_Info:
r = ParseQuotedString(s, &buf);
if (r != OK) {
return r;
}
r = AppendTrigInfo(trig, DBufValue(&buf));
DBufFree(&buf);
if (r) return r;
break;
case T_Tag:
r = ParseToken(s, &buf);
if (r) return r;
@@ -1180,7 +1190,7 @@ int TriggerReminder(ParsePtr p, Trigger *t, TimeTrig *tim, int dse, int is_queue
return OK;
}
FromDSE(dse, &y, &m, &d);
sprintf(tmpBuf, "%04d/%02d/%02d ", y, m+1, d);
snprintf(tmpBuf, sizeof(tmpBuf), "%04d/%02d/%02d ", y, m+1, d);
if (DBufPuts(&calRow, tmpBuf) != OK) {
DBufFree(&calRow);
DBufFree(&pre_buf);
@@ -1201,9 +1211,9 @@ int TriggerReminder(ParsePtr p, Trigger *t, TimeTrig *tim, int dse, int is_queue
DBufPuts(&calRow, "* ");
}
if (tim->duration != NO_TIME) {
sprintf(tmpBuf, "%d ", tim->duration);
snprintf(tmpBuf, sizeof(tmpBuf), "%d ", tim->duration);
} else {
sprintf(tmpBuf, "* ");
snprintf(tmpBuf, sizeof(tmpBuf), "* ");
}
if (DBufPuts(&calRow, tmpBuf) != OK) {
DBufFree(&calRow);
@@ -1211,9 +1221,9 @@ int TriggerReminder(ParsePtr p, Trigger *t, TimeTrig *tim, int dse, int is_queue
return E_NO_MEM;
}
if (tim->ttime != NO_TIME) {
sprintf(tmpBuf, "%d ", tim->ttime);
snprintf(tmpBuf, sizeof(tmpBuf), "%d ", tim->ttime);
} else {
sprintf(tmpBuf, "* ");
snprintf(tmpBuf, sizeof(tmpBuf), "* ");
}
if (DBufPuts(&calRow, tmpBuf) != OK) {
DBufFree(&calRow);
@@ -1263,7 +1273,7 @@ int TriggerReminder(ParsePtr p, Trigger *t, TimeTrig *tim, int dse, int is_queue
/* Don't use msgprefix() on RUN-type reminders */
if (t->typ != RUN_TYPE) {
if (UserFuncExists("msgprefix") == 1) {
sprintf(PrioExpr, "msgprefix(%d)", t->priority);
snprintf(PrioExpr, sizeof(PrioExpr), "msgprefix(%d)", t->priority);
s = PrioExpr;
r = EvalExpr(&s, &v, NULL);
if (!r) {
@@ -1289,7 +1299,7 @@ int TriggerReminder(ParsePtr p, Trigger *t, TimeTrig *tim, int dse, int is_queue
if (t->typ != RUN_TYPE) {
if (UserFuncExists("msgsuffix") == 1) {
sprintf(PrioExpr, "msgsuffix(%d)", t->priority);
snprintf(PrioExpr, sizeof(PrioExpr), "msgsuffix(%d)", t->priority);
s = PrioExpr;
r = EvalExpr(&s, &v, NULL);
if (!r) {
@@ -1694,7 +1704,7 @@ static int ShouldTriggerBasedOnWarn(Trigger *t, int dse, int *err)
return (dse == DSEToday);
}
for (i=1; ; i++) {
sprintf(buffer, "%s(%d)", t->warn, i);
snprintf(buffer, sizeof(buffer), "%s(%d)", t->warn, i);
s = buffer;
r = EvalExpr(&s, &v, NULL);
if (r) {

View File

@@ -1546,6 +1546,8 @@ static int parse_expr_token(DynamicBuffer *buf, char const **in)
{
char c;
char c2;
char hexbuf[3];
DBufFree(buf);
@@ -1633,8 +1635,32 @@ static int parse_expr_token(DynamicBuffer *buf, char const **in)
case 'v':
r = DBufPutc(buf, '\v');
break;
case 'x':
c2 = *(*in + 1);
if (!isxdigit(c2)) {
r = DBufPutc(buf, **in);
break;
}
hexbuf[0] = c2;
hexbuf[1] = 0;
(*in)++;
c2 = *(*in + 1);
if (isxdigit(c2)) {
hexbuf[1] = c2;
hexbuf[2] = 0;
(*in)++;
}
c2 = (int) strtol(hexbuf, NULL, 16);
if (!c2) {
Eprint(tr("\\x00 is not a valid escape sequence"));
r = E_PARSE_ERR;
} else {
r = DBufPutc(buf, c2);
}
break;
default:
r = DBufPutc(buf, **in);
break;
}
(*in)++;
if (r != OK) {
@@ -1877,8 +1903,8 @@ static expr_node * parse_function_call(char const **e, int *r, Var *locals, int
}
}
}
ptr = *e;
if (TOKEN_IS(")")) {
ptr = *e;
*r = GET_TOKEN();
if (*r != OK) {
return free_expr_tree(node);
@@ -3014,12 +3040,12 @@ int DoCoerce(char type, Value *v)
}
case STR_TYPE:
switch(v->type) {
case INT_TYPE: sprintf(coerce_buf, "%d", v->v.val); break;
case TIME_TYPE: sprintf(coerce_buf, "%02d%c%02d", v->v.val / 60,
case INT_TYPE: snprintf(coerce_buf, sizeof(coerce_buf), "%d", v->v.val); break;
case TIME_TYPE: snprintf(coerce_buf, sizeof(coerce_buf), "%02d%c%02d", v->v.val / 60,
TimeSep, v->v.val % 60);
break;
case DATE_TYPE: FromDSE(v->v.val, &y, &m, &d);
sprintf(coerce_buf, "%04d%c%02d%c%02d",
snprintf(coerce_buf, sizeof(coerce_buf), "%04d%c%02d%c%02d",
y, DateSep, m+1, DateSep, d);
break;
case DATETIME_TYPE:
@@ -3028,7 +3054,7 @@ int DoCoerce(char type, Value *v)
k = v->v.val % MINUTES_PER_DAY;
h = k / 60;
i = k % 60;
sprintf(coerce_buf, "%04d%c%02d%c%02d%c%02d%c%02d",
snprintf(coerce_buf, sizeof(coerce_buf), "%04d%c%02d%c%02d%c%02d%c%02d",
y, DateSep, m+1, DateSep, d, DateTimeSep, h, TimeSep, i);
break;
default: return E_CANT_COERCE;

View File

@@ -93,6 +93,7 @@ static int FDefined (func_info *);
static int FDosubst (func_info *);
static int FDusk (func_info *);
static int FEasterdate (func_info *);
static int FEscape (func_info *);
static int FEvalTrig (func_info *);
static int FFiledate (func_info *);
static int FFiledatetime (func_info *);
@@ -252,6 +253,7 @@ BuiltinFunc Func[] = {
{ "dosubst", 1, 3, 0, FDosubst, NULL },
{ "dusk", 0, 1, 0, FDusk, NULL },
{ "easterdate", 0, 1, 0, FEasterdate, NULL },
{ "escape", 1, 2, 1, FEscape, NULL },
{ "evaltrig", 1, 2, 0, FEvalTrig, NULL },
{ "filedate", 1, 1, 0, FFiledate, NULL },
{ "filedatetime", 1, 1, 0, FFiledatetime, NULL },
@@ -1076,7 +1078,7 @@ static int FOrd(func_info *info)
if (u == 1 && t != 11) s = "st";
if (u == 2 && t != 12) s = "nd";
if (u == 3 && t != 13) s = "rd";
sprintf(buf, "%d%s", v, s);
snprintf(buf, sizeof(buf), "%d%s", v, s);
return RetStrVal(buf, info);
}
@@ -1828,10 +1830,10 @@ static int FTrigger(func_info *info)
FromDSE(date, &y, &m, &d);
if (tim != NO_TIME) {
sprintf(buf, "%d %s %d AT %02d:%02d", d, MonthName[m], y,
snprintf(buf, sizeof(buf), "%d %s %d AT %02d:%02d", d, MonthName[m], y,
tim/60, tim%60);
} else {
sprintf(buf, "%d %s %d", d, MonthName[m], y);
snprintf(buf, sizeof(buf), "%d %s %d", d, MonthName[m], y);
}
return RetStrVal(buf, info);
}
@@ -2363,6 +2365,89 @@ static int FHebyear(func_info *info)
return OK;
}
/****************************************************************/
/* */
/* escape - escape special characters with "\xx" sequences */
/* */
/****************************************************************/
static int FEscape(func_info *info)
{
DynamicBuffer dbuf;
char const *s;
char hexbuf[16];
int r;
int include_quotes = 0;
ASSERT_TYPE(0, STR_TYPE);
if (Nargs >= 2) {
ASSERT_TYPE(1, INT_TYPE);
include_quotes = ARGV(1);
}
DBufInit(&dbuf);
if (include_quotes) {
r = DBufPutc(&dbuf, '"');
if (r != OK) {
DBufFree(&dbuf);
return r;
}
}
s = ARGSTR(0);
while(*s) {
switch(*s) {
case '\a':
r = DBufPuts(&dbuf, "\\a");
break;
case '\b':
r = DBufPuts(&dbuf, "\\b");
break;
case '\f':
r = DBufPuts(&dbuf, "\\f");
break;
case '\n':
r = DBufPuts(&dbuf, "\\n");
break;
case '\r':
r = DBufPuts(&dbuf, "\\r");
break;
case '\t':
r = DBufPuts(&dbuf, "\\t");
break;
case '\v':
r = DBufPuts(&dbuf, "\\v");
break;
case '\\':
r = DBufPuts(&dbuf, "\\\\");
break;
case '"':
r = DBufPuts(&dbuf, "\\\"");
break;
default:
if ((*s > 0 && *s < ' ') || *s == 0x7f) {
snprintf(hexbuf, sizeof(hexbuf), "\\x%02x", (unsigned int) *s);
r = DBufPuts(&dbuf, hexbuf);
} else {
r = DBufPutc(&dbuf, *s);
}
break;
}
if (r != OK) {
DBufFree(&dbuf);
return r;
}
s++;
}
if (include_quotes) {
r = DBufPutc(&dbuf, '"');
if (r != OK) {
DBufFree(&dbuf);
return r;
}
}
r = RetStrVal(DBufValue(&dbuf), info);
DBufFree(&dbuf);
return r;
}
/****************************************************************/
/* */
/* htmlescape - replace <. > and & by &lt; &gt; and &amp; */
@@ -3012,6 +3097,7 @@ static int FPsshade(func_info *info)
char psbuff[256];
char *s = psbuff;
int i;
size_t len = sizeof(psbuff);
/* 1 or 3 args */
if (Nargs != 1 && Nargs != 3) return E_2MANY_ARGS;
@@ -3027,16 +3113,19 @@ static int FPsshade(func_info *info)
Wprint(tr("psshade() is deprecated; use SPECIAL SHADE instead."));
}
sprintf(s, "/_A LineWidth 2 div def ");
snprintf(s, len, "/_A LineWidth 2 div def ");
len -= strlen(s);
s += strlen(s);
sprintf(s, "_A _A moveto ");
snprintf(s, len, "_A _A moveto ");
len -= strlen(s);
s += strlen(s);
sprintf(s, "BoxWidth _A sub _A lineto BoxWidth _A sub BoxHeight _A sub lineto ");
snprintf(s, len, "BoxWidth _A sub _A lineto BoxWidth _A sub BoxHeight _A sub lineto ");
len -= strlen(s);
s += strlen(s);
if (Nargs == 1) {
sprintf(s, "_A BoxHeight _A sub lineto closepath %d 100 div setgray fill 0.0 setgray", ARGV(0));
snprintf(s, len, "_A BoxHeight _A sub lineto closepath %d 100 div setgray fill 0.0 setgray", ARGV(0));
} else {
sprintf(s, "_A BoxHeight _A sub lineto closepath %d 100 div %d 100 div %d 100 div setrgbcolor fill 0.0 setgray", ARGV(0), ARGV(1), ARGV(2));
snprintf(s, len, "_A BoxHeight _A sub lineto closepath %d 100 div %d 100 div %d 100 div setrgbcolor fill 0.0 setgray", ARGV(0), ARGV(1), ARGV(2));
}
return RetStrVal(psbuff, info);
}
@@ -3059,6 +3148,7 @@ static int FPsmoon(func_info *info)
char const *extra = NULL;
int size = -1;
int fontsize = -1;
size_t len = sizeof(psbuff);
ASSERT_TYPE(0, INT_TYPE);
if (ARGV(0) < 0) return E_2LOW;
@@ -3082,60 +3172,71 @@ static int FPsmoon(func_info *info)
Wprint(tr("psmoon() is deprecated; use SPECIAL MOON instead."));
}
if (size > 0) {
sprintf(sizebuf, "%d", size);
snprintf(sizebuf, sizeof(sizebuf), "%d", size);
} else {
strcpy(sizebuf, "DaySize 2 div");
}
if (fontsize > 0) {
sprintf(fontsizebuf, "%d", fontsize);
snprintf(fontsizebuf, sizeof(fontsizebuf), "%d", fontsize);
} else {
strcpy(fontsizebuf, "EntrySize");
}
sprintf(s, "gsave 0 setgray newpath Border %s add BoxHeight Border sub %s sub",
snprintf(s, len, "gsave 0 setgray newpath Border %s add BoxHeight Border sub %s sub",
sizebuf, sizebuf);
len -= strlen(s);
s += strlen(s);
sprintf(s, " %s 0 360 arc closepath", sizebuf);
snprintf(s, len, " %s 0 360 arc closepath", sizebuf);
len -= strlen(s);
s += strlen(s);
switch(ARGV(0)) {
case 0:
sprintf(s, " fill");
snprintf(s, len, " fill");
len -= strlen(s);
s += strlen(s);
break;
case 2:
sprintf(s, " stroke");
snprintf(s, len, " stroke");
len -= strlen(s);
s += strlen(s);
break;
case 1:
sprintf(s, " stroke");
snprintf(s, len, " stroke");
len -= strlen(s);
s += strlen(s);
sprintf(s, " newpath Border %s add BoxHeight Border sub %s sub",
snprintf(s, len, " newpath Border %s add BoxHeight Border sub %s sub",
sizebuf, sizebuf);
len -= strlen(s);
s += strlen(s);
sprintf(s, " %s 90 270 arc closepath fill", sizebuf);
snprintf(s, len, " %s 90 270 arc closepath fill", sizebuf);
len -= strlen(s);
s += strlen(s);
break;
default:
sprintf(s, " stroke");
snprintf(s, len, " stroke");
len -= strlen(s);
s += strlen(s);
sprintf(s, " newpath Border %s add BoxHeight Border sub %s sub",
snprintf(s, len, " newpath Border %s add BoxHeight Border sub %s sub",
sizebuf, sizebuf);
len -= strlen(s);
s += strlen(s);
sprintf(s, " %s 270 90 arc closepath fill", sizebuf);
snprintf(s, len, " %s 270 90 arc closepath fill", sizebuf);
len -= strlen(s);
s += strlen(s);
break;
}
if (extra) {
sprintf(s, " Border %s add %s add Border add BoxHeight border sub %s sub %s sub moveto /EntryFont findfont %s scalefont setfont (%s) show",
snprintf(s, len, " Border %s add %s add Border add BoxHeight border sub %s sub %s sub moveto /EntryFont findfont %s scalefont setfont (%s) show",
sizebuf, sizebuf, sizebuf, sizebuf, fontsizebuf, extra);
len -= strlen(s);
s += strlen(s);
}
sprintf(s, " grestore");
snprintf(s, len, " grestore");
return RetStrVal(psbuff, info);
}
@@ -3266,7 +3367,7 @@ static int FDatepart(func_info *info)
* used for the timezone stuff! */
static int setenv(char const *varname, char const *val, int overwrite)
{
static char tzbuf[256];
static char tzbuf[128];
if (strcmp(varname, "TZ")) {
fprintf(ErrFp, "built-in setenv can only be used with TZ\n");
abort();
@@ -3279,7 +3380,7 @@ static int setenv(char const *varname, char const *val, int overwrite)
if (strlen(val) > 250) {
return -1;
}
sprintf(tzbuf, "%s=%s", varname, val);
snprintf(tzbuf, sizeof(tzbuf), "%s=%s", varname, val);
return(putenv(tzbuf));
}
#endif
@@ -3288,12 +3389,12 @@ static int setenv(char const *varname, char const *val, int overwrite)
* used for the timezone stuff! */
static void unsetenv(char const *varname)
{
static char tzbuf[8];
static char tzbuf[128];
if (strcmp(varname, "TZ")) {
fprintf(ErrFp, "built-in unsetenv can only be used with TZ\n");
abort();
}
sprintf(tzbuf, "%s", varname);
snprintf(tzbuf, sizeof(tzbuf), "%s", varname);
putenv(tzbuf);
}
#endif

View File

@@ -176,6 +176,9 @@ EXTERN INIT( unsigned int FuncRecursionLevel, 0);
/* Suppress warnings about implicit REM and MSG */
EXTERN INIT( int SuppressImplicitRemWarnings, 0);
/* Test mode - used by the acceptance tests */
EXTERN INIT( int TestMode, 0);
extern int NumFullOmits, NumPartialOmits;
/* List of months */

View File

@@ -795,10 +795,11 @@ void InitRemind(int argc, char const *argv[])
}
/* Figure out the offset from UTC */
if (CalculateUTC)
/* Figure out the offset from UTC */
if (CalculateUTC) {
(void) CalcMinsFromUTC(DSEToday, MinutesPastMidnight(0),
&MinsFromUTC, NULL);
}
}
/***************************************************************/
@@ -1121,6 +1122,17 @@ static void
ProcessLongOption(char const *arg)
{
int t;
if (!strcmp(arg, "test")) {
fprintf(stderr, "Enabling test mode: This is meant for the acceptance test.\nDo not use --test in production.\nIn test mode, the system time is fixed at 2025-01-06@19:00\n");
TestMode = 1;
/* Update RealToday because of TestMode */
RealToday = SystemDate(&CurYear, &CurMon, &CurDay);
DSEToday = RealToday;
FromDSE(DSEToday, &CurYear, &CurMon, &CurDay);
return;
}
if (!strcmp(arg, "version")) {
printf("%s\n", VERSION);
exit(EXIT_SUCCESS);

View File

@@ -299,7 +299,7 @@ json_value * json_parse_ex (json_settings * settings,
if (flags & flag_string)
{
if (!b)
{ sprintf (error, "Unexpected EOF in string (at %u:%u)", line_and_col);
{ snprintf (error, sizeof(error), "Unexpected EOF in string (at %u:%u)", line_and_col);
goto e_failed;
}
@@ -325,7 +325,7 @@ json_value * json_parse_ex (json_settings * settings,
(uc_b3 = hex_value (*++ state.ptr)) == 0xFF ||
(uc_b4 = hex_value (*++ state.ptr)) == 0xFF)
{
sprintf (error, "Invalid character value `%c` (at %u:%u)", b, line_and_col);
snprintf (error, sizeof(error), "Invalid character value `%c` (at %u:%u)", b, line_and_col);
goto e_failed;
}
@@ -342,7 +342,7 @@ json_value * json_parse_ex (json_settings * settings,
(uc_b3 = hex_value (*++ state.ptr)) == 0xFF ||
(uc_b4 = hex_value (*++ state.ptr)) == 0xFF)
{
sprintf (error, "Invalid character value `%c` (at %u:%u)", b, line_and_col);
snprintf (error, sizeof(error), "Invalid character value `%c` (at %u:%u)", b, line_and_col);
goto e_failed;
}
@@ -472,7 +472,7 @@ json_value * json_parse_ex (json_settings * settings,
if (flags & flag_block_comment)
{
if (!b)
{ sprintf (error, "%u:%u: Unexpected EOF in block comment", line_and_col);
{ snprintf (error, sizeof(error), "%u:%u: Unexpected EOF in block comment", line_and_col);
goto e_failed;
}
@@ -488,12 +488,12 @@ json_value * json_parse_ex (json_settings * settings,
else if (b == '/')
{
if (! (flags & (flag_seek_value | flag_done)) && top->type != json_object)
{ sprintf (error, "%u:%u: Comment not allowed here", line_and_col);
{ snprintf (error, sizeof(error), "%u:%u: Comment not allowed here", line_and_col);
goto e_failed;
}
if (++ state.ptr == end)
{ sprintf (error, "%u:%u: EOF unexpected", line_and_col);
{ snprintf (error, sizeof(error), "%u:%u: EOF unexpected", line_and_col);
goto e_failed;
}
@@ -508,7 +508,7 @@ json_value * json_parse_ex (json_settings * settings,
continue;
default:
sprintf (error, "%u:%u: Unexpected `%c` in comment opening sequence", line_and_col, b);
snprintf (error, sizeof(error), "%u:%u: Unexpected `%c` in comment opening sequence", line_and_col, b);
goto e_failed;
};
}
@@ -526,7 +526,7 @@ json_value * json_parse_ex (json_settings * settings,
default:
sprintf (error, "%u:%u: Trailing garbage: `%c`",
snprintf (error, sizeof(error), "%u:%u: Trailing garbage: `%c`",
state.cur_line, state.cur_col, b);
goto e_failed;
@@ -545,7 +545,7 @@ json_value * json_parse_ex (json_settings * settings,
if (top && top->type == json_array)
flags = (flags & ~ (flag_need_comma | flag_seek_value)) | flag_next;
else
{ sprintf (error, "%u:%u: Unexpected ]", line_and_col);
{ snprintf (error, sizeof(error), "%u:%u: Unexpected ]", line_and_col);
goto e_failed;
}
@@ -561,7 +561,7 @@ json_value * json_parse_ex (json_settings * settings,
}
else
{
sprintf (error, "%u:%u: Expected , before %c",
snprintf (error, sizeof(error), "%u:%u: Expected , before %c",
state.cur_line, state.cur_col, b);
goto e_failed;
@@ -576,7 +576,7 @@ json_value * json_parse_ex (json_settings * settings,
}
else
{
sprintf (error, "%u:%u: Expected : before %c",
snprintf (error, sizeof(error), "%u:%u: Expected : before %c",
state.cur_line, state.cur_col, b);
goto e_failed;
@@ -702,7 +702,7 @@ json_value * json_parse_ex (json_settings * settings,
continue;
}
else
{ sprintf (error, "%u:%u: Unexpected %c when seeking value", line_and_col, b);
{ snprintf (error, sizeof(error), "%u:%u: Unexpected %c when seeking value", line_and_col, b);
goto e_failed;
}
};
@@ -722,7 +722,7 @@ json_value * json_parse_ex (json_settings * settings,
case '"':
if (flags & flag_need_comma)
{ sprintf (error, "%u:%u: Expected , before \"", line_and_col);
{ snprintf (error, sizeof(error), "%u:%u: Expected , before \"", line_and_col);
goto e_failed;
}
@@ -747,7 +747,7 @@ json_value * json_parse_ex (json_settings * settings,
}
/* FALLTHROUGH */
default:
sprintf (error, "%u:%u: Unexpected `%c` in object", line_and_col, b);
snprintf (error, sizeof(error), "%u:%u: Unexpected `%c` in object", line_and_col, b);
goto e_failed;
};
@@ -765,7 +765,7 @@ json_value * json_parse_ex (json_settings * settings,
if (! (flags & flag_num_e))
{
if (flags & flag_num_zero)
{ sprintf (error, "%u:%u: Unexpected `0` before `%c`", line_and_col, b);
{ snprintf (error, sizeof(error), "%u:%u: Unexpected `0` before `%c`", line_and_col, b);
goto e_failed;
}
@@ -814,7 +814,7 @@ json_value * json_parse_ex (json_settings * settings,
else if (b == '.' && top->type == json_integer)
{
if (!num_digits)
{ sprintf (error, "%u:%u: Expected digit before `.`", line_and_col);
{ snprintf (error, sizeof(error), "%u:%u: Expected digit before `.`", line_and_col);
goto e_failed;
}
@@ -831,7 +831,7 @@ json_value * json_parse_ex (json_settings * settings,
if (top->type == json_double)
{
if (!num_digits)
{ sprintf (error, "%u:%u: Expected digit after `.`", line_and_col);
{ snprintf (error, sizeof(error), "%u:%u: Expected digit after `.`", line_and_col);
goto e_failed;
}
@@ -857,7 +857,7 @@ json_value * json_parse_ex (json_settings * settings,
else
{
if (!num_digits)
{ sprintf (error, "%u:%u: Expected digit after `e`", line_and_col);
{ snprintf (error, sizeof(error), "%u:%u: Expected digit after `e`", line_and_col);
goto e_failed;
}
@@ -942,7 +942,7 @@ json_value * json_parse_ex (json_settings * settings,
e_unknown_value:
sprintf (error, "%u:%u: Unknown value", line_and_col);
snprintf (error, sizeof(error), "%u:%u: Unknown value", line_and_col);
goto e_failed;
e_alloc_failure:
@@ -952,7 +952,7 @@ e_alloc_failure:
e_overflow:
sprintf (error, "%u:%u: Too long (caught overflow)", line_and_col);
snprintf (error, sizeof(error), "%u:%u: Too long (caught overflow)", line_and_col);
goto e_failed;
e_failed:

View File

@@ -630,7 +630,9 @@ int ParseTokenOrQuotedString(ParsePtr p, DynamicBuffer *dbuf)
/***************************************************************/
int ParseQuotedString(ParsePtr p, DynamicBuffer *dbuf)
{
int c, err;
int c, err, c2;
char hexbuf[3];
DBufFree(dbuf);
c = ParseNonSpaceChar(p, &err, 0);
if (err) return err;
@@ -674,6 +676,34 @@ int ParseQuotedString(ParsePtr p, DynamicBuffer *dbuf)
case 'v':
err = DBufPutc(dbuf, '\v');
break;
case 'x':
/* \x Followed by one or two hex digits */
c2 = ParseChar(p, &err, 1);
if (err) break;
if (!isxdigit(c2)) {
err = DBufPutc(dbuf, c);
break;
}
hexbuf[0] = c2;
hexbuf[1] = 0;
c2 = ParseChar(p, &err, 0);
if (err) break;
c2 = ParseChar(p, &err, 1);
if (err) break;
if (isxdigit(c2)) {
hexbuf[1] = c2;
hexbuf[2] = 0;
c2 = ParseChar(p, &err, 0);
if (err) break;
}
c2 = (int) strtol(hexbuf, NULL, 16);
if (!c2) {
Eprint(tr("\\x00 is not a valid escape sequence"));
err = E_PARSE_ERR;
} else {
err = DBufPutc(dbuf, c2);
}
break;
default:
err = DBufPutc(dbuf, c);
}
@@ -1010,6 +1040,11 @@ int SystemTime(int realtime)
if (!realtime && (SysTime != -1)) return SysTime;
if (TestMode) {
/* Pretend it's 7:00PM in test mode */
return 19 * 3600;
}
now = time(NULL);
t = localtime(&now);
return t->tm_hour * 3600L + t->tm_min * 60L +
@@ -1043,6 +1078,14 @@ int SystemDate(int *y, int *m, int *d)
time_t now;
struct tm *t;
/* In test mode, always return 6 January 2025 */
if (TestMode) {
*y = 2025;
*m = 0;
*d = 6;
return 12789; /* 2025-01-06 */
}
now = time(NULL);
t = localtime(&now);
@@ -1902,6 +1945,8 @@ void
FreeTrig(Trigger *t)
{
DBufFree(&(t->tags));
FreeTrigInfoChain(t->infos);
t->infos = NULL;
}
void
@@ -1928,7 +1973,7 @@ ClearLastTriggers(void)
LastTrigger.omitfunc[0] = 0;
LastTrigger.passthru[0] = 0;
DBufFree(&(LastTrigger.tags));
LastTrigger.infos = NULL;
LastTimeTrig.ttime = NO_TIME;
LastTimeTrig.delta = NO_DELTA;
LastTimeTrig.rep = NO_REP;

View File

@@ -218,7 +218,7 @@ int IsOmitted(int dse, int localomit, char const *omitfunc, int *omit)
Value v;
FromDSE(dse, &y, &m, &d);
sprintf(expr, "%s('%04d-%02d-%02d')",
snprintf(expr, sizeof(expr), "%s('%04d-%02d-%02d')",
omitfunc, y, m+1, d);
s = expr;
r = EvalExpr(&s, &v, NULL);
@@ -388,6 +388,7 @@ int DoOmit(ParsePtr p)
case T_RemType:
case T_Priority:
case T_Tag:
case T_Info:
case T_Duration:
DBufFree(&buf);
parsing = 0;
@@ -415,7 +416,7 @@ int DoOmit(ParsePtr p)
return E_2MANY_LOCALOMIT;
}
WeekdayOmits |= wd;
if (tok.type == T_Tag || tok.type == T_Duration || tok.type == T_RemType || tok.type == T_Priority) return E_PARSE_AS_REM;
if (tok.type == T_Tag || tok.type == T_Info || tok.type == T_Duration || tok.type == T_RemType || tok.type == T_Priority) return E_PARSE_AS_REM;
return OK;
}
@@ -500,7 +501,7 @@ int DoOmit(ParsePtr p)
}
}
if (tok.type == T_Tag || tok.type == T_Duration || tok.type == T_RemType || tok.type == T_Priority) return E_PARSE_AS_REM;
if (tok.type == T_Tag || tok.type == T_Info || tok.type == T_Duration || tok.type == T_RemType || tok.type == T_Priority) return E_PARSE_AS_REM;
return OK;
}

View File

@@ -273,6 +273,12 @@ int GetTranslatedStringTryingVariants(char const *orig, DynamicBuffer *out);
char const *GetErr(int r);
char const *tr(char const *s);
void print_escaped_string(FILE *fp, char const *s);
void print_escaped_string_helper(FILE *fp, char const *s, int esc_for_remind);
void print_escaped_string_helper(FILE *fp, char const *s, int esc_for_remind, int json);
void GenerateSysvarTranslationTemplates(void);
void TranslationTemplate(char const *msg);
TrigInfo *NewTrigInfo(char const *i);
void FreeTrigInfo(TrigInfo *ti);
void FreeTrigInfoChain(TrigInfo *ti);
int AppendTrigInfo(Trigger *t, char const *info);
int TrigInfoHeadersAreTheSame(char const *i1, char const *i2);
int TrigInfoIsValid(char const *info);

View File

@@ -163,6 +163,7 @@ static void del_reminder(QueuedRem *qid)
if (q == qid) {
QueueHead = q->next;
if (q->text) free((void *) q->text);
FreeTrig(&(q->t));
free(q);
return;
}
@@ -171,6 +172,7 @@ static void del_reminder(QueuedRem *qid)
if (q->next == qid) {
q->next = q->next->next;
if (next->text) free((void *) next->text);
FreeTrig(&(next->t));
free(next);
return;
}
@@ -227,6 +229,10 @@ int QueueReminder(ParsePtr p, Trigger *trig,
strcpy(qelem->passthru, trig->passthru);
qelem->tt = *tim;
qelem->t = *trig;
/* Take over infos */
trig->infos = NULL;
DBufInit(&(qelem->t.tags));
DBufPuts(&(qelem->t.tags), DBufValue(&(trig->tags)));
if (SynthesizeTags) {
@@ -460,7 +466,11 @@ void HandleQueuedReminders(void)
if (IsServerMode() && q->typ != RUN_TYPE) {
if (DaemonJSON) {
printf("{\"response\":\"reminder\",");
snprintf(qid, sizeof(qid), "%lx", (unsigned long) q);
if (TestMode) {
snprintf(qid, sizeof(qid), "42424242");
} else {
snprintf(qid, sizeof(qid), "%lx", (unsigned long) q);
}
PrintJSONKeyPairString("qid", qid);
PrintJSONKeyPairString("ttime", SimpleTimeNoSpace(q->tt.ttime));
PrintJSONKeyPairString("now", SimpleTimeNoSpace(MinutesPastMidnight(1)));
@@ -486,6 +496,7 @@ void HandleQueuedReminders(void)
DefaultColorB = q->blue;
/* Make a COPY of q->t because TriggerReminder can change q->t.typ */
Trigger tcopy = q->t;
if (DaemonJSON) {
DynamicBuffer out;
DBufInit(&out);
@@ -698,7 +709,7 @@ static int CalculateNextTimeUsingSched(QueuedRem *q)
to be a security hole! */
while(1) {
char exprBuf[VAR_NAME_LEN+32];
sprintf(exprBuf, "%s(%d)", q->sched, q->ntrig);
snprintf(exprBuf, sizeof(exprBuf), "%s(%d)", q->sched, q->ntrig);
s = exprBuf;
r = EvalExpr(&s, &v, NULL);
if (r) {
@@ -760,7 +771,11 @@ json_queue(QueuedRem const *q)
printf("{");
WriteJSONTrigger(&(q->t), 1, DSEToday);
WriteJSONTimeTrigger(&(q->tt));
snprintf(idbuf, sizeof(idbuf), "%lx", (unsigned long) q);
if (TestMode) {
snprintf(idbuf, sizeof(idbuf), "42424242");
} else {
snprintf(idbuf, sizeof(idbuf), "%lx", (unsigned long) q);
}
PrintJSONKeyPairString("qid", idbuf);
PrintJSONKeyPairInt("rundisabled", q->RunDisabled);
PrintJSONKeyPairInt("ntrig", q->ntrig);

View File

@@ -1222,7 +1222,7 @@ int DoQueuedPs(void)
if (moonsize < 0) {
size = "DaySize 2 div";
} else {
sprintf(buffer, "%d", moonsize);
snprintf(buffer, sizeof(buffer), "%d", moonsize);
size = buffer;
}
@@ -1235,7 +1235,7 @@ int DoQueuedPs(void)
if (fontsize < 0) {
fsize = "EntrySize";
} else {
sprintf(fbuffer, "%d", fontsize);
snprintf(fbuffer, sizeof(fbuffer), "%d", fontsize);
fsize = fbuffer;
}
printf("/EntryFont findfont %s scalefont setfont (",
@@ -1273,7 +1273,7 @@ int DoQueuedPs(void)
if (fontsize < 0) {
fsize = "EntrySize";
} else {
sprintf(fbuffer, "%d", fontsize);
snprintf(fbuffer, sizeof(fbuffer), "%d", fontsize);
fsize = fbuffer;
}
printf("/EntryFont findfont %s scalefont setfont (",

View File

@@ -183,7 +183,7 @@ static void IssueSortBanner(int dse)
if (UserFuncExists("sortbanner") != 1) return;
FromDSE(dse, &y, &m, &d);
sprintf(BanExpr, "sortbanner('%04d/%02d/%02d')", y, m+1, d);
snprintf(BanExpr, sizeof(BanExpr), "sortbanner('%04d/%02d/%02d')", y, m+1, d);
y = EvalExpr(&s, &v, NULL);
if (y) return;
if (DoCoerce(STR_TYPE, &v)) return;

View File

@@ -71,6 +71,7 @@ Token TokArray[] = {
{ "in", 2, T_In, 0 },
{ "include", 3, T_Include, 0 },
{ "includecmd", 10, T_IncludeCmd, 0 },
{ "info", 4, T_Info, 0 },
{ "january", 3, T_Month, 0 },
{ "july", 3, T_Month, 6 },
{ "june", 3, T_Month, 5 },

View File

@@ -48,10 +48,10 @@ TranslationTemplate(char const *in)
}
printf("TRANSLATE ");
print_escaped_string_helper(stdout, in, 1);
print_escaped_string_helper(stdout, in, 1, 0);
if (FindTranslation(in)) {
printf(" ");
print_escaped_string_helper(stdout, tr(in), 1);
print_escaped_string_helper(stdout, tr(in), 1, 0);
printf("\n");
} else {
printf(" \"\"\n");
@@ -66,7 +66,7 @@ GenerateTranslationTemplate(void)
printf("# Translation table template\n\n");
printf("TRANSLATE \"LANGID\" ");
print_escaped_string_helper(stdout, tr("LANGID"), 1);
print_escaped_string_helper(stdout, tr("LANGID"), 1, 0);
printf("\n\n");
printf("BANNER %s\n", DBufValue(&Banner));
@@ -74,14 +74,14 @@ GenerateTranslationTemplate(void)
printf("\n# Weekday Names\n");
for (i=0; i<7; i++) {
printf("SET $%s ", DayName[i]);
print_escaped_string_helper(stdout, tr(DayName[i]), 1);
print_escaped_string_helper(stdout, tr(DayName[i]), 1, 0);
printf("\n");
}
printf("\n# Month Names\n");
for (i=0; i<12; i++) {
printf("SET $%s ", MonthName[i]);
print_escaped_string_helper(stdout, tr(MonthName[i]), 1);
print_escaped_string_helper(stdout, tr(MonthName[i]), 1, 0);
printf("\n");
}
@@ -178,11 +178,17 @@ ClearTranslationTable(void)
void
print_escaped_string(FILE *fp, char const *s)
{
print_escaped_string_helper(fp, s, 0);
print_escaped_string_helper(fp, s, 0, 0);
}
static void
print_escaped_string_json(FILE *fp, char const *s)
{
print_escaped_string_helper(fp, s, 0, 1);
}
void
print_escaped_string_helper(FILE *fp, char const *s, int esc_for_remind) {
print_escaped_string_helper(FILE *fp, char const *s, int esc_for_remind, int json) {
putc('"', fp);
while(*s) {
switch(*s) {
@@ -196,11 +202,20 @@ print_escaped_string_helper(FILE *fp, char const *s, int esc_for_remind) {
case '"': putc('\\', fp); putc('"', fp); break;
case '\\': putc('\\', fp); putc('\\', fp); break;
default:
if (esc_for_remind && *s == '[') {
fprintf(fp, "[\"[\"]");
if ((*s > 0 && *s < 32) || *s == 0x7f) {
if (json) {
fprintf(fp, "\\u%04x", (unsigned int) *s);
} else {
fprintf(fp, "\\x%02x", (unsigned int) *s);
}
} else {
putc(*s, fp); break;
if (esc_for_remind && *s == '[') {
fprintf(fp, "[\"[\"]");
} else {
putc(*s, fp);
}
}
break;
}
s++;
}
@@ -245,9 +260,9 @@ DumpTranslationTable(FILE *fp, int json)
fprintf(fp, ",");
}
done=1;
print_escaped_string(fp, item->orig);
print_escaped_string_json(fp, item->orig);
fprintf(fp, ":");
print_escaped_string(fp, item->translated);
print_escaped_string_json(fp, item->translated);
}
item = hash_table_next(&TranslationTable, item);
}

View File

@@ -12,8 +12,9 @@
#include "config.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "types.h"
#include "protos.h"
#include "globals.h"
@@ -668,3 +669,126 @@ int ComputeTriggerNoAdjustDuration(int today, Trigger *trig, TimeTrig *tim,
return -1;
}
/***************************************************************/
/* */
/* NewTrigInfo */
/* */
/* Create a new TrigInfo object with the specified contents. */
/* Returns NULL if memory allocation fails. */
/* */
/***************************************************************/
TrigInfo *
NewTrigInfo(char const *i)
{
TrigInfo *ti = malloc(sizeof(TrigInfo));
if (!ti) {
return NULL;
}
ti->next = NULL;
ti->info = StrDup(i);
if (!ti->info) {
free(ti);
return NULL;
}
return ti;
}
/***************************************************************/
/* */
/* FreeTrigInfo */
/* */
/* Free a TrigInfo objects. */
/* */
/***************************************************************/
void
FreeTrigInfo(TrigInfo *ti)
{
if (ti->info) {
free( (void *) ti->info);
}
free(ti);
}
void
FreeTrigInfoChain(TrigInfo *ti)
{
TrigInfo *next;
while(ti) {
next = ti->next;
FreeTrigInfo(ti);
ti = next;
}
}
/***************************************************************/
/* */
/* AppendTrigInfo */
/* */
/* Append an info item to a trigger. */
/* */
/***************************************************************/
int
AppendTrigInfo(Trigger *t, char const *info)
{
TrigInfo *ti;
TrigInfo *last;
if (!TrigInfoIsValid(info)) {
Eprint("%s", tr("Invalid INFO string: Must be of the form \"Header: Value\""));
return E_PARSE_ERR;
}
ti = NewTrigInfo(info);
last = t->infos;
if (!ti) {
return E_NO_MEM;
}
if (!last) {
t->infos = ti;
return OK;
}
if (TrigInfoHeadersAreTheSame(info, last->info)) {
Eprint("%s", tr("Duplicate INFO headers are not permitted"));
FreeTrigInfo(ti);
return E_PARSE_ERR;
}
while (last->next) {
last = last->next;
if (TrigInfoHeadersAreTheSame(info, last->info)) {
Eprint("%s", tr("Duplicate INFO headers are not permitted"));
FreeTrigInfo(ti);
return E_PARSE_ERR;
}
}
last->next = ti;
return OK;
}
int
TrigInfoHeadersAreTheSame(char const *i1, char const *i2)
{
char const *c1 = strchr(i1, ':');
char const *c2 = strchr(i2, ':');
if (!c1 || !c2) return 1;
if (c1 - i1 != c2 - i2) return 0;
if (!strncasecmp(i1, i2, (c1 - i1))) return 1;
return 0;
}
int
TrigInfoIsValid(char const *info)
{
char const *t;
char const *s = strchr(info, ':');
if (!s) return 0;
if (s == info) return 0;
t = info;
while (t < s) {
if (isspace(*t) || iscntrl(*t)) return 0;
t++;
}
return 1;
}

View File

@@ -107,6 +107,11 @@ typedef struct var {
Value v;
} Var;
typedef struct triginfo {
struct triginfo *next;
char const *info;
} TrigInfo;
/* A trigger */
typedef struct {
int expired;
@@ -138,6 +143,7 @@ typedef struct {
char omitfunc[VAR_NAME_LEN+1]; /* OMITFUNC function */
DynamicBuffer tags;
char passthru[PASSTHRU_LEN+1];
TrigInfo *infos;
} Trigger;
/* A time trigger */
@@ -217,9 +223,9 @@ enum TokTypes
T_Date, T_DateTime, T_Day, T_Debug, T_Delta, T_Dumpvars, T_Duration,
T_Else, T_Empty, T_EndIf, T_ErrMsg, T_Exit, T_Expr,
T_Flush, T_Frename, T_Fset, T_Funset, T_If, T_IfTrig, T_In,
T_Include, T_IncludeCmd, T_IncludeR, T_IncludeSys, T_LastBack, T_LongTime,
T_MaybeUncomputable, T_Month, T_NoQueue, T_Number, T_Omit, T_OmitFunc,
T_Once, T_Ordinal, T_Pop, T_Preserve, T_Priority, T_Push,T_Rem,
T_Include, T_IncludeCmd, T_IncludeR, T_IncludeSys, T_Info, T_LastBack,
T_LongTime, T_MaybeUncomputable, T_Month, T_NoQueue, T_Number, T_Omit,
T_OmitFunc, T_Once, T_Ordinal, T_Pop, T_Preserve, T_Priority, T_Push,T_Rem,
T_RemType, T_Rep, T_Scanfrom, T_Sched, T_Set, T_Skip, T_Tag, T_Through,
T_Time, T_Translate, T_UnSet, T_Until, T_Warn, T_WkDay, T_Year
};

View File

@@ -1268,13 +1268,13 @@ void GenerateSysvarTranslationTemplates(void)
continue;
}
printf("SET $%s ", SysVarArr[i].name);
print_escaped_string_helper(stdout, tr(msg), 1);
print_escaped_string_helper(stdout, tr(msg), 1, 0);
printf("\n");
} else if (!strcmp(SysVarArr[i].name, "Hplu") ||
!strcmp(SysVarArr[i].name, "Mplu")) {
msg = * (char const **) SysVarArr[i].value;
printf("SET $%s ", SysVarArr[i].name);
print_escaped_string_helper(stdout, tr(msg), 1);
print_escaped_string_helper(stdout, tr(msg), 1, 0);
printf("\n");
}
}

View File

@@ -1,6 +1,6 @@
FSET msgprefix(x) "Priority: " + x + "; Filename: " + filename() + ": "
REM at 23:56 MSG foo
REM PRIORITY 42 at 23:57 MSG bar
REM PRIORITY 999 at 23:58 MSG quux
REM PRIORITY 42 at 23:57 INFO "Info: yuppers" MSG bar
REM PRIORITY 999 at 23:58 INFO "Info2: Nope" INFO "Info3: heh" MSG quux
DO queue2.rem

View File

@@ -32,18 +32,10 @@ fi
# ../src/remind. This trick was suggested by Jochen Sprickerhof
alias remind="echo You should be using ../src/remind explicitly in test-rem >&2; exit 1"
# Set a known timezone so moon phases show up in predictable places
TZ=UTC
export TZ
DO_QUEUE_TESTS=1
RESULT=`(echo 'BANNER %'; echo 'IF now() > 23:55'; echo 'MSG late%'; echo 'ENDIF') | ../src/remind -h -`
if test "$RESULT" = "late" ; then
DO_QUEUE_TESTS=0
fi
# If we're already in a utf-8 locale, do
# nothing; otherwise, set LC_ALL
OK=0
@@ -474,22 +466,9 @@ rm -rf include_dir/ww
../src/remind --version >> ../tests/test.out 2>&1
# Test queueing. Because eventstart depends on the actual system
# date, we have to convert it to some constant (in this case,
# VOLATILE) so that tests are not dependent on the system date.
if test "$DO_QUEUE_TESTS" = 1 ; then
echo JSONQUEUE | ../src/remind -z0 ../tests/queue1.rem 2>&1 | sed -e 's/"eventstart":"................"/"eventstart":"VOLATILE"/g' | sed -e 's/"qid":"[0-9a-f]*",//g' >> ../tests/test.out 2>&1
echo QUEUE | ../src/remind -zj ../tests/queue1.rem 2>&1 | sed -e 's/"eventstart":"................"/"eventstart":"VOLATILE"/g' | sed -e 's/"qid":"[0-9a-f]*",//g' >> ../tests/test.out 2>&1
else
echo "*** Skipping queueing tests between 23:55 and 00:00 UTC"
echo "*** If you want to run these tests, wait until 00:00 UTC"
# Fake the output we expect from above
cat >> ../tests/test.out <<'EOF'
NOTE JSONQUEUE
[{"priority":2,"eventstart":"VOLATILE","time":"23:59","nexttime":"23:59","tdelta":0,"trep":0,"rundisabled":0,"ntrig":1,"filename":"../tests/queue2.rem","lineno":1,"type":"MSG_TYPE","body":"XXXX"},{"priority":999,"eventstart":"VOLATILE","time":"23:58","nexttime":"23:58","tdelta":0,"trep":0,"rundisabled":0,"ntrig":1,"filename":"../tests/queue1.rem","lineno":5,"type":"MSG_TYPE","body":"quux"},{"priority":42,"eventstart":"VOLATILE","time":"23:57","nexttime":"23:57","tdelta":0,"trep":0,"rundisabled":0,"ntrig":1,"filename":"../tests/queue1.rem","lineno":4,"type":"MSG_TYPE","body":"bar"},{"priority":5000,"eventstart":"VOLATILE","time":"23:56","nexttime":"23:56","tdelta":0,"trep":0,"rundisabled":0,"ntrig":1,"filename":"../tests/queue1.rem","lineno":3,"type":"MSG_TYPE","body":"foo"}]
NOTE ENDJSONQUEUE
{"response":"queue","queue":[{"priority":2,"eventstart":"VOLATILE","time":"23:59","nexttime":"23:59","tdelta":0,"trep":0,"rundisabled":0,"ntrig":1,"filename":"../tests/queue2.rem","lineno":1,"type":"MSG_TYPE","body":"XXXX"},{"priority":999,"eventstart":"VOLATILE","time":"23:58","nexttime":"23:58","tdelta":0,"trep":0,"rundisabled":0,"ntrig":1,"filename":"../tests/queue1.rem","lineno":5,"type":"MSG_TYPE","body":"quux"},{"priority":42,"eventstart":"VOLATILE","time":"23:57","nexttime":"23:57","tdelta":0,"trep":0,"rundisabled":0,"ntrig":1,"filename":"../tests/queue1.rem","lineno":4,"type":"MSG_TYPE","body":"bar"},{"priority":5000,"eventstart":"VOLATILE","time":"23:56","nexttime":"23:56","tdelta":0,"trep":0,"rundisabled":0,"ntrig":1,"filename":"../tests/queue1.rem","lineno":3,"type":"MSG_TYPE","body":"foo"}],"command":"QUEUE"}
EOF
fi
# date, we use the --test flag to fake the date and time.
echo JSONQUEUE | ../src/remind --test -z0 ../tests/queue1.rem >> ../tests/test.out 2>&1
echo QUEUE | ../src/remind --test -zj ../tests/queue1.rem >> ../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
@@ -656,6 +635,70 @@ EOF
TRANSLATE GENERATE
EOF
# Make sure stupidly-long translations of "am" and "pm" can't cause a
# segmentation fault
../src/remind -c - 1 Feb 2024 <<'EOF' >> ../tests/test.out 2>&1
TRANS "am" "alsdkjalksdj alksjd alksdj alksjd laksjd laksjd laksjd laksjd laksjd laksjd laksjd laksjd lkasjd laksjd laksjd lkajs dlkajs dlkasj dlkasjd lkajsd lkajs dlkasjd lkasj dlkajsd lkasjd lkasjd laksjd laksjd laksjd alskdj alskdj alksdj alksdj alskdj alksdj aslkdj"
TRANS "pm" "oiwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwjwwwwwwwwwwwwwwwjwpqoejkpqwojepqowjepqojwepqowjepqowjepqowjepqowjepqowjepqowjepqojwepqowjepqowjepqowjepqowjepqowjeqpweoj"
REM WED AT 11:00 MSG wookie
REM WED AT 13:00 MSG blah
EOF
# The INFO keyword
../src/remind -pp - 1 Feb 2024 <<'EOF' >> ../tests/test.out 2>&1
REM Wed INFO "Location: here" INFO "Summary: Nope" MSG Meeting
EOF
# Invalid info strings
../src/remind - 1 Feb 2024 <<'EOF' >> ../tests/test.out 2>&1
REM Thu INFO "Invalid" MSG wookie
REM Fri INFO ": foo" MSG blat
REM Sun INFO "foo bar baz : blork" MSG uua
# Duplicate info string
REM Sat INFO "Location: here" INFO "location: there" MSG blort
EOF
# Test parsing of quoted strings and the "escape" function
../src/remind - 1 Feb 2024 <<'EOF' >> ../tests/test.out 2>&1
BANNER %
SET $AddBlankLines 0
TRANSLATE "foo" "test: \\\" \a\b\f\\n\r\t\v\x3\x1b haha"
REM msg foo translation = %(foo)
FLUSH
set a "vartest: \\\" \a\b\f\\n\r\t\v\x3\x1b haha"
REM msg a = [a]
FLUSH
dump a
set b escape(a)
REM msg b = [b]
set b escape(a,1)
REM msg b = [b]
FLUSH
dump b
FLUSH
set a "\x"
dump a
FLUSH
set a "\xPOO"
dump a
FLUSH
set a "\x0"
set a "\x00"
set a "\x0P"
set a "\x00P"
EOF
# Test translate table dumping
../src/remind - 1 Feb 2024 <<EOF >> ../tests/test.out 2>&1
TRANSLATE "\x03" "BREAK"
TRANSLATE DUMP
EOF
../src/remind -ppp - 1 Feb 2024 <<EOF >> ../tests/test.out 2>&1
TRANSLATE "\x03" "BREAK"
EOF
# Languages
for i in ../include/lang/??.rem ; do
../src/remind -r -q "-ii=\"$i\"" ../tests/tstlang.rem 1 Feb 2024 13:34 >> ../tests/test.out 2>&1

View File

@@ -1047,7 +1047,7 @@ set a057 value("a05"+"6")
"a05" + "6" => "a056"
value("a056") => "SDFJHSDF KSJDFH KJSDFH KSJDFH"
set a058 version()
version() => "05.02.03"
version() => "05.03.00"
set a059 wkday(today())
today() => 1991-02-16
wkday(1991-02-16) => "Saturday"
@@ -2611,7 +2611,7 @@ a056 "SDFJHSDF KSJDFH KJSDFH KSJDFH"
a007 "1991-02-16"
a057 "SDFJHSDF KSJDFH KJSDFH KSJDFH"
a008 "11:44"
a058 "05.02.03"
a058 "05.03.00"
a059 "Saturday"
a010 12
a060 6
@@ -5632,8 +5632,8 @@ REM SATISFY ""
REM SATISFY [version() > "01.00.00"]
../tests/test.rem(1050): SATISFY: expression has no reference to trigdate() or $T...
../tests/test.rem(1050): Trig = Saturday, 16 February, 1991
version() => "05.02.03"
"05.02.03" > "01.00.00" => 1
version() => "05.03.00"
"05.03.00" > "01.00.00" => 1
../tests/test.rem(1050): Trig(satisfied) = Saturday, 16 February, 1991
REM SATISFY [max(x, max(x, 1, 2, 3), 4, 5, 6) * 5]
../tests/test.rem(1051): SATISFY: expression has no reference to trigdate() or $T...
@@ -23200,11 +23200,17 @@ SECURITY: Won't read world-writable file or directory!
Error reading include_dir/ww: Can't open file
SECURITY: Won't read world-writable file or directory!
Error reading include_dir/ww: No files matching *.rem
05.02.03
05.03.00
Enabling test mode: This is meant for the acceptance test.
Do not use --test in production.
In test mode, the system time is fixed at 2025-01-06@19:00
NOTE JSONQUEUE
[{"priority":2,"eventstart":"VOLATILE","time":"23:59","nexttime":"23:59","tdelta":0,"trep":0,"rundisabled":0,"ntrig":1,"filename":"../tests/queue2.rem","lineno":1,"type":"MSG_TYPE","body":"XXXX"},{"priority":999,"eventstart":"VOLATILE","time":"23:58","nexttime":"23:58","tdelta":0,"trep":0,"rundisabled":0,"ntrig":1,"filename":"../tests/queue1.rem","lineno":5,"type":"MSG_TYPE","body":"quux"},{"priority":42,"eventstart":"VOLATILE","time":"23:57","nexttime":"23:57","tdelta":0,"trep":0,"rundisabled":0,"ntrig":1,"filename":"../tests/queue1.rem","lineno":4,"type":"MSG_TYPE","body":"bar"},{"priority":5000,"eventstart":"VOLATILE","time":"23:56","nexttime":"23:56","tdelta":0,"trep":0,"rundisabled":0,"ntrig":1,"filename":"../tests/queue1.rem","lineno":3,"type":"MSG_TYPE","body":"foo"}]
[{"priority":2,"eventstart":"2025-01-06T23:59","time":"23:59","nexttime":"23:59","tdelta":0,"trep":0,"qid":"42424242","rundisabled":0,"ntrig":1,"filename":"../tests/queue2.rem","lineno":1,"type":"MSG_TYPE","body":"XXXX"},{"priority":999,"eventstart":"2025-01-06T23:58","info":{"info2":"Nope","info3":"heh"},"time":"23:58","nexttime":"23:58","tdelta":0,"trep":0,"qid":"42424242","rundisabled":0,"ntrig":1,"filename":"../tests/queue1.rem","lineno":5,"type":"MSG_TYPE","body":"quux"},{"priority":42,"eventstart":"2025-01-06T23:57","info":{"info":"yuppers"},"time":"23:57","nexttime":"23:57","tdelta":0,"trep":0,"qid":"42424242","rundisabled":0,"ntrig":1,"filename":"../tests/queue1.rem","lineno":4,"type":"MSG_TYPE","body":"bar"},{"priority":5000,"eventstart":"2025-01-06T23:56","time":"23:56","nexttime":"23:56","tdelta":0,"trep":0,"qid":"42424242","rundisabled":0,"ntrig":1,"filename":"../tests/queue1.rem","lineno":3,"type":"MSG_TYPE","body":"foo"}]
NOTE ENDJSONQUEUE
{"response":"queue","queue":[{"priority":2,"eventstart":"VOLATILE","time":"23:59","nexttime":"23:59","tdelta":0,"trep":0,"rundisabled":0,"ntrig":1,"filename":"../tests/queue2.rem","lineno":1,"type":"MSG_TYPE","body":"XXXX"},{"priority":999,"eventstart":"VOLATILE","time":"23:58","nexttime":"23:58","tdelta":0,"trep":0,"rundisabled":0,"ntrig":1,"filename":"../tests/queue1.rem","lineno":5,"type":"MSG_TYPE","body":"quux"},{"priority":42,"eventstart":"VOLATILE","time":"23:57","nexttime":"23:57","tdelta":0,"trep":0,"rundisabled":0,"ntrig":1,"filename":"../tests/queue1.rem","lineno":4,"type":"MSG_TYPE","body":"bar"},{"priority":5000,"eventstart":"VOLATILE","time":"23:56","nexttime":"23:56","tdelta":0,"trep":0,"rundisabled":0,"ntrig":1,"filename":"../tests/queue1.rem","lineno":3,"type":"MSG_TYPE","body":"foo"}],"command":"QUEUE"}
Enabling test mode: This is meant for the acceptance test.
Do not use --test in production.
In test mode, the system time is fixed at 2025-01-06@19:00
{"response":"queue","queue":[{"priority":2,"eventstart":"2025-01-06T23:59","time":"23:59","nexttime":"23:59","tdelta":0,"trep":0,"qid":"42424242","rundisabled":0,"ntrig":1,"filename":"../tests/queue2.rem","lineno":1,"type":"MSG_TYPE","body":"XXXX"},{"priority":999,"eventstart":"2025-01-06T23:58","info":{"info2":"Nope","info3":"heh"},"time":"23:58","nexttime":"23:58","tdelta":0,"trep":0,"qid":"42424242","rundisabled":0,"ntrig":1,"filename":"../tests/queue1.rem","lineno":5,"type":"MSG_TYPE","body":"quux"},{"priority":42,"eventstart":"2025-01-06T23:57","info":{"info":"yuppers"},"time":"23:57","nexttime":"23:57","tdelta":0,"trep":0,"qid":"42424242","rundisabled":0,"ntrig":1,"filename":"../tests/queue1.rem","lineno":4,"type":"MSG_TYPE","body":"bar"},{"priority":5000,"eventstart":"2025-01-06T23:56","time":"23:56","nexttime":"23:56","tdelta":0,"trep":0,"qid":"42424242","rundisabled":0,"ntrig":1,"filename":"../tests/queue1.rem","lineno":3,"type":"MSG_TYPE","body":"foo"}],"command":"QUEUE"}
BANNER %
REM 29 MSG One
-(2): Trig = Thursday, 29 February, 2024
@@ -23975,6 +23981,7 @@ in
inc
include
includecmd
info
last
lastday
lastworkday
@@ -24084,6 +24091,7 @@ defined
dosubst
dusk
easterdate
escape
evaltrig
filedate
filedatetime
@@ -24496,6 +24504,7 @@ TRANSLATE "Cannot open `%s' for writing: %s" ""
TRANSLATE "Cannot stat %s - not running as daemon!" ""
TRANSLATE "Cannot use AT clause in multitrig() function" ""
TRANSLATE "Do not use ["["]] around expression in SET command" ""
TRANSLATE "Duplicate INFO headers are not permitted" ""
TRANSLATE "Error: THROUGH date earlier than start date" ""
TRANSLATE "Executing `%s' for INCLUDECMD and caching as `%s'" ""
TRANSLATE "Found cached directory listing for `%s'" ""
@@ -24503,6 +24512,7 @@ TRANSLATE "Function `%s' defined at %s:%d should take %d argument%s, but actuall
TRANSLATE "Function `%s' redefined (previously defined at %s:%d)" ""
TRANSLATE "GetValidHebDate: Bad adarbehave value %d" ""
TRANSLATE "In" ""
TRANSLATE "Invalid INFO string: Must be of the form \"Header: Value\"" ""
TRANSLATE "Invalid translation: Both original and translated must have the same printf-style formatting sequences in the same order." ""
TRANSLATE "Missing REM type; assuming MSG" ""
TRANSLATE "No Adar A in %d" ""
@@ -24535,11 +24545,160 @@ TRANSLATE "Warning: Unterminated %%{...} substitution sequence" ""
TRANSLATE "Warning: Useless use of UNTIL with fully-specified date and no *rep" ""
TRANSLATE "Warning: Variable name `%.*s...' truncated to `%.*s'" ""
TRANSLATE "You have OMITted everything! The space-time continuum is at risk." ""
TRANSLATE "\\x00 is not a valid escape sequence" ""
TRANSLATE "did you mean" ""
TRANSLATE "here" ""
TRANSLATE "psmoon() is deprecated; use SPECIAL MOON instead." ""
TRANSLATE "psshade() is deprecated; use SPECIAL SHADE instead." ""
TRANSLATE "remaining call frames omitted" ""
+----------------------------------------------------------------------------+
| February 2024 |
+----------+----------+----------+----------+----------+----------+----------+
| Sunday | Monday | Tuesday |Wednesday | Thursday | Friday | Saturday |
+----------+----------+----------+----------+----------+----------+----------+
| | | | |1 |2 |3 |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
+----------+----------+----------+----------+----------+----------+----------+
|4 |5 |6 |7 |8 |9 |10 |
| | | | | | | |
| | | |11:00alsdk| | | |
| | | |jalksdj | | | |
| | | |alksjd | | | |
| | | |alksdj | | | |
| | | |alksjd | | | |
| | | |laksjd | | | |
| | | |laksjd | | | |
| | | |laksjd | | | |
| | | |laksjd la | | | |
| | | |wookie | | | |
| | | | | | | |
| | | |1:00oiwwww| | | |
| | | |wwwwwwwwww| | | |
| | | |wwwwwwwwww| | | |
| | | |wwwwwwwwww| | | |
| | | |wjwwwwwwww| | | |
| | | |wwwwwwwjwp| | | |
| | | |qoejkpqw | | | |
| | | |blah | | | |
+----------+----------+----------+----------+----------+----------+----------+
|11 |12 |13 |14 |15 |16 |17 |
| | | | | | | |
| | | |11:00alsdk| | | |
| | | |jalksdj | | | |
| | | |alksjd | | | |
| | | |alksdj | | | |
| | | |alksjd | | | |
| | | |laksjd | | | |
| | | |laksjd | | | |
| | | |laksjd | | | |
| | | |laksjd la | | | |
| | | |wookie | | | |
| | | | | | | |
| | | |1:00oiwwww| | | |
| | | |wwwwwwwwww| | | |
| | | |wwwwwwwwww| | | |
| | | |wwwwwwwwww| | | |
| | | |wjwwwwwwww| | | |
| | | |wwwwwwwjwp| | | |
| | | |qoejkpqw | | | |
| | | |blah | | | |
+----------+----------+----------+----------+----------+----------+----------+
|18 |19 |20 |21 |22 |23 |24 |
| | | | | | | |
| | | |11:00alsdk| | | |
| | | |jalksdj | | | |
| | | |alksjd | | | |
| | | |alksdj | | | |
| | | |alksjd | | | |
| | | |laksjd | | | |
| | | |laksjd | | | |
| | | |laksjd | | | |
| | | |laksjd la | | | |
| | | |wookie | | | |
| | | | | | | |
| | | |1:00oiwwww| | | |
| | | |wwwwwwwwww| | | |
| | | |wwwwwwwwww| | | |
| | | |wwwwwwwwww| | | |
| | | |wjwwwwwwww| | | |
| | | |wwwwwwwjwp| | | |
| | | |qoejkpqw | | | |
| | | |blah | | | |
+----------+----------+----------+----------+----------+----------+----------+
|25 |26 |27 |28 |29 | | |
| | | | | | | |
| | | |11:00alsdk| | | |
| | | |jalksdj | | | |
| | | |alksjd | | | |
| | | |alksdj | | | |
| | | |alksjd | | | |
| | | |laksjd | | | |
| | | |laksjd | | | |
| | | |laksjd | | | |
| | | |laksjd la | | | |
| | | |wookie | | | |
| | | | | | | |
| | | |1:00oiwwww| | | |
| | | |wwwwwwwwww| | | |
| | | |wwwwwwwwww| | | |
| | | |wwwwwwwwww| | | |
| | | |wjwwwwwwww| | | |
| | | |wwwwwwwjwp| | | |
| | | |qoejkpqw | | | |
| | | |blah | | | |
+----------+----------+----------+----------+----------+----------+----------+
# translations
{"LANGID":"en"}
# rem2ps2 begin
February 2024 29 4 0
Sunday Monday Tuesday Wednesday Thursday Friday Saturday
January 31
March 31
{"date":"2024-02-07","filename":"-","lineno":1,"info":{"location":"here","summary":"Nope"},"wd":["Wednesday"],"priority":5000,"body":"Meeting"}
{"date":"2024-02-14","filename":"-","lineno":1,"info":{"location":"here","summary":"Nope"},"wd":["Wednesday"],"priority":5000,"body":"Meeting"}
{"date":"2024-02-21","filename":"-","lineno":1,"info":{"location":"here","summary":"Nope"},"wd":["Wednesday"],"priority":5000,"body":"Meeting"}
{"date":"2024-02-28","filename":"-","lineno":1,"info":{"location":"here","summary":"Nope"},"wd":["Wednesday"],"priority":5000,"body":"Meeting"}
# rem2ps2 end
-stdin-(1): Invalid INFO string: Must be of the form "Header: Value"
-stdin-(2): Invalid INFO string: Must be of the form "Header: Value"
-stdin-(3): Invalid INFO string: Must be of the form "Header: Value"
-stdin-(6): Duplicate INFO headers are not permitted
No reminders.
foo translation = test: \"  \n  haha
a = vartest: \"  \n  haha
Variable Value
a "vartest: \\\" \a\b\f\\n\r\t\v\x03\x1b haha"
b = vartest: \\\" \a\b\f\\n\r\t\v\x03\x1b haha
b = "vartest: \\\" \a\b\f\\n\r\t\v\x03\x1b haha"
Variable Value
b "\"vartest: \\\\\\\" \\a\\b\\f\\\\n\\r\\t\\v\\x03\\x1b h"...
Variable Value
a "x"
Variable Value
a "xPOO"
-stdin-(23): \x00 is not a valid escape sequence
-stdin-(24): \x00 is not a valid escape sequence
-stdin-(25): \x00 is not a valid escape sequence
-stdin-(26): \x00 is not a valid escape sequence
# Translation table
TRANSLATE "LANGID" "en"
TRANSLATE "\x03" "BREAK"
No reminders.
[
{
"translations":{"LANGID":"en","\u0003":"BREAK"},"caltype":"monthly","monthname":"February","year":2024,"daysinmonth":29,"firstwkday":4,"mondayfirst":0,"daynames":["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],"prevmonthname":"January","daysinprevmonth":31,"prevmonthyear":2024,"nextmonthname":"March","daysinnextmonth":31,"nextmonthyear":2024,"entries":[
]
}
]
Agenda pel dijous, 1 de febrer de 2024:
Language: ca