Compare commits

...

25 Commits

Author SHA1 Message Date
Dianne Skoll
7bf23912ae Improve man page slightly. 2025-08-15 15:46:08 -04:00
Dianne Skoll
6ae0340137 Document tkremind bug fix. 2025-08-15 15:36:59 -04:00
Dianne Skoll
0e48ace855 Update WHATSNEW. 2025-08-15 15:35:57 -04:00
Dianne Skoll
d1f1ddf5b7 Document the TODO introspection functions. 2025-08-15 15:33:04 -04:00
Dianne Skoll
5f9e227dc8 Document and test overriding of %: %! %? %@ %# 2025-08-15 15:28:23 -04:00
Dianne Skoll
b770676cb6 Add tests for trigistodo, trigcompletethrough, trigmaxoverdue 2025-08-15 15:22:39 -04:00
Dianne Skoll
5ee415c2fb Add trigistodo(), trigcompletethrough() and trigmaxoverdue() introspection functions. 2025-08-15 15:20:57 -04:00
Dianne Skoll
6c2a4b66fd Add tests and translations for %: 2025-08-15 15:07:32 -04:00
Dianne Skoll
6c2d65c08e Allow non-alphanumeric substitution sequences to be overridden. 2025-08-15 14:55:03 -04:00
Dianne Skoll
cd2dc3bea3 Add test for %: 2025-08-15 14:35:31 -04:00
Dianne Skoll
b1b80316ab Implement %: substitution sequence. 2025-08-15 11:54:31 -04:00
Dianne Skoll
f04835cf6f Another eval that should be a catch. 2025-08-15 08:34:41 -04:00
Dianne Skoll
fb19ea6b7e Use "catch" instead of "eval". What was I thinking?? 2025-08-15 08:33:49 -04:00
Dianne Skoll
e2d7796d4a Clarify wording. 2025-08-14 21:53:06 -04:00
Dianne Skoll
de2ec1aa7b In --json mode, redirect any RUN ... command stdout to stderr
We really don't want to mess up the JSON we produce on stdout!!
2025-08-14 21:47:40 -04:00
Dianne Skoll
b9fb215d9d Don't try to process stderr output as JSON. :) 2025-08-14 20:52:38 -04:00
Dianne Skoll
a62ed0e0c5 Always output well-formed JSON in --json mode. 2025-08-14 20:43:35 -04:00
Dianne Skoll
eceb5e3f82 Suppress some output in --json mode. 2025-08-14 20:35:07 -04:00
Dianne Skoll
d5aa93ae57 Document TkRemind support for max-overdue. 2025-08-14 19:05:17 -04:00
Dianne Skoll
a66da78b4a Add support for MAX-OVERDUE to TkRemind. 2025-08-14 19:03:32 -04:00
Dianne Skoll
af69f54bff Add max_overdue to JSON. 2025-08-14 18:54:15 -04:00
Dianne Skoll
a000a7f17e Add MAX-OVERDUE feature. 2025-08-14 18:50:39 -04:00
Dianne Skoll
582f388500 Disable ANSI colors in JSON mode. 2025-08-14 13:57:23 -04:00
Dianne Skoll
7762f4f2d6 Add $HideCompletedTodos, $JSONMode and $TodoFilter system variables. 2025-08-14 13:56:02 -04:00
Dianne Skoll
31c9b2afb7 Disable ANSI color sequences in -p mode. 2025-08-14 13:43:32 -04:00
41 changed files with 531 additions and 53 deletions

View File

@@ -114,7 +114,7 @@
"DURATION" "ELSE" "ENDIF" "ERRMSG" "EXIT" "EXPR" "FIRST"
"FLUSH" "FOURTH" "FRENAME" "FROM" "FSET" "FUNSET" "IF"
"IFTRIG" "IN" "INC" "INCLUDE" "INCLUDECMD" "INFO" "LAST"
"LASTDAY" "LASTWORKDAY" "MAYBE" "MAYBE-UNCOMPUTABLE" "MSF"
"LASTDAY" "LASTWORKDAY" "MAX-OVERDUE" "MAYBE" "MAYBE-UNCOMPUTABLE" "MSF"
"MSG" "NOQUEUE" "OMIT" "OMITFUNC" "ONCE" "POP"
"POP-OMIT-CONTEXT" "POP-FUNCS" "POP-VARS" "PRESERVE" "PRIORITY" "PS"
"PSFILE" "PUSH" "PUSH-FUNCS" "PUSH-VARS" "PUSH-OMIT-CONTEXT" "REM" "RETURN"
@@ -137,8 +137,8 @@
"$DefaultPrio" "$DefaultTDelta" "$DeltaOverride"
"$DontFork" "$DontQueue" "$DontTrigAts" "$EndSent" "$EndSentIg"
"$ExpressionTimeLimit" "$February" "$FirstIndent" "$FoldYear"
"$FormWidth" "$Friday" "$Fromnow" "$Hour" "$Hplu" "$HushMode"
"$IgnoreOnce" "$InfDelta" "$IntMax" "$IntMin" "$Is" "$January" "$July"
"$FormWidth" "$Friday" "$Fromnow" "$HideCompletedTodos" "$Hour" "$Hplu" "$HushMode"
"$IgnoreOnce" "$InfDelta" "$IntMax" "$IntMin" "$Is" "$January" "$JSONMode" "$July"
"$June" "$LatDeg" "$Latitude" "$LatMin" "$LatSec" "$Location"
"$LongDeg" "$Longitude" "$LongMin" "$LongSec" "$March" "$MaxFullOmits"
"$MaxLateMinutes" "$MaxPartialOmits" "$MaxSatIter" "$MaxStringLen"
@@ -148,7 +148,7 @@
"$PrefixLineNo" "$PSCal" "$RunOff" "$Saturday" "$September"
"$SimpleCal" "$SortByDate" "$SortByPrio" "$SortByTime" "$SubsIndent"
"$Sunday" "$SuppressImplicitWarnings" "$SuppressLRM" "$SysInclude" "$T" "$Tb" "$Td"
"$TerminalBackground" "$Thursday" "$TimeSep" "$TimetIs64bit" "$Tm" "$Today"
"$TerminalBackground" "$Thursday" "$TimeSep" "$TimetIs64bit" "$Tm" "$Today" "$TodoFilter"
"$Tomorrow" "$Tt" "$Tuesday" "$Tw" "$Ty" "$U" "$Ud" "$Um"
"$UntimedFirst" "$Use256Colors" "$UseBGVTColors" "$UseTrueColors"
"$UseVTColors" "$Uw" "$Uy" "$Was" "$Wednesday")
@@ -178,9 +178,9 @@
"ostype" "pad" "plural" "psmoon" "psshade" "realcurrent" "realnow"
"realtoday" "rows" "sgn" "shell" "shellescape" "slide" "soleq"
"stdout" "strlen" "substr" "sunrise" "sunset" "time" "timepart"
"timezone" "today" "trig" "trigback" "trigbase" "trigdate" "trigdatetime"
"timezone" "today" "trig" "trigback" "trigbase" "trigcompletethrough" "trigdate" "trigdatetime"
"trigdelta" "trigduration" "trigeventduration" "trigeventstart"
"trigfrom" "trigger" "triginfo" "trigpriority" "trigrep"
"trigfrom" "trigger" "triginfo" "trigistodo" "trigmaxoverdue" "trigpriority" "trigrep"
"trigscanfrom" "trigtags" "trigtime" "trigtimedelta" "trigtimerep"
"triguntil" "trigvalid" "typeof" "tzconvert" "upper" "utctolocal"
"value" "version" "weekno" "wkday" "wkdaynum" "year"

View File

@@ -15,6 +15,11 @@ CHANGES TO REMIND
the rest of the current file. Useful for early exit from an
INCLUDEd file.
- MINOR NEW FEATURE: remind: Allow the %:, %!, %?, %@ and %#
substitution sequences to be overridden by defining the functions
subst_colon, subst_bang, subst_question, subst_at and subst_hash,
respectively.
- MAJOR IMPROVEMENTS: tkremind: TkRemind has been given an overhaul.
The "Show Queue" and "Show Today's Reminders" windows now respect
the color scheme. TkRemind lets you create TODO reminders and
@@ -41,8 +46,11 @@ CHANGES TO REMIND
- MINOR IMPROVEMENT: include/lang/nl.rem: Use "eergisteren" for "2
days ago" and "overmorgen" for "in 2 days' time."
- MINOR IMPROVEMENT: tkremind: TkRemind now passes all command-line options
back to Remind.
- MINOR IMPROVEMENT: tkremind: TkRemind now passes all command-line
options back to Remind.
- BUG FIX: tkremind: In a couple of places, the "eval" command was used
where the intention was to use "catch". I blame Perl...
- BUG FIX: remind: SCANFROM and FROM are separated out internally, and
in the JSON output, the original FROM or SCANFROM value is

View File

@@ -6,8 +6,8 @@
# SPDX-License-Identifier: GPL-2.0-only
if !defined("ansi_bold")
# Disable ANSI attributes in calandar mode
if $CalMode
# Disable ANSI attributes in calendar mode or JSON mode
if $CalMode || $PSCal || $JSONMode
set ansi_normal ""
set ansi_bold ""
set ansi_faint ""

View File

@@ -31,6 +31,7 @@ SET $Tomorrow "demà"
TRANSLATE "yesterday" "ahir"
TRANSLATE "are" "són"
TRANSLATE "were" "eren"
TRANSLATE "done" "completada"
FSET subst_bx(a,d,t) iif(d==today()+2, "demà passat", d >= today(), "d'aquí " + (d-today()) + " dies", "fa " + (today()-d) + " dies")

View File

@@ -32,6 +32,7 @@ SET $Tomorrow "i morgen"
TRANSLATE "yesterday" "i går"
TRANSLATE "are" "er"
TRANSLATE "were" "var"
TRANSLATE "done" "fuldført"
BANNER Påmindelse for %w, %d. %m, %y%o:

View File

@@ -34,6 +34,7 @@ SET $Tomorrow "morgen"
TRANSLATE "yesterday" "gestern"
TRANSLATE "are" "sind"
TRANSLATE "were" "waren"
TRANSLATE "done" "abgeschlossen"
# Banner
BANNER Termine für %w, den %d. %m %y%o:

View File

@@ -32,6 +32,7 @@ SET $Tomorrow "mañana"
TRANSLATE "yesterday" "ayer"
TRANSLATE "are" "son"
TRANSLATE "were" "eran"
TRANSLATE "done" "completada"
BANNER Agenda para el %w, %d%s %m, %y%o:

View File

@@ -32,6 +32,7 @@ SET $Tomorrow "huomenna"
TRANSLATE "yesterday" "eilen"
TRANSLATE "are" "ovat"
TRANSLATE "were" "olivat"
TRANSLATE "done" "suoritettu"
BANNER Viestit %wna %d. %mta %y%o:

View File

@@ -32,6 +32,7 @@ SET $Tomorrow "demain"
TRANSLATE "yesterday" "hier"
TRANSLATE "are" "sont"
TRANSLATE "were" "étaient"
TRANSLATE "done" "accomplie"
SET $On "le"
SET $At "à"

View File

@@ -32,6 +32,7 @@ SET $Tomorrow "αύριο"
TRANSLATE "yesterday" "εχθές"
TRANSLATE "are" "είναι"
TRANSLATE "were" "ήταν"
TRANSLATE "done" "ολοκληρώθηκε"
BANNER Υπενθυμίσεις: %w, %d %m, %y%o:

View File

@@ -32,6 +32,7 @@ SET $Tomorrow "á morgun"
TRANSLATE "yesterday" "í gær"
TRANSLATE "are" "eru"
TRANSLATE "were" "voru"
TRANSLATE "done" "lokið"
BANNER Minnisatriði: %w, %d%s %m, %y%o:

View File

@@ -32,6 +32,7 @@ SET $Tomorrow "domani"
TRANSLATE "yesterday" "Ieri"
TRANSLATE "are" "sono"
TRANSLATE "were" "erano"
TRANSLATE "done" "completato"
BANNER Promemoria per %w, %d %m %y%o:

View File

@@ -31,6 +31,7 @@ SET $Tomorrow "morgen"
TRANSLATE "yesterday" "gisteren"
TRANSLATE "are" "zijn"
TRANSLATE "were" "waren"
TRANSLATE "done" "voltooid"
BANNER Herinneringen voor %w, %d %m, %y%o:

View File

@@ -32,6 +32,7 @@ SET $Tomorrow "i morgen"
TRANSLATE "yesterday" "i går"
TRANSLATE "are" "er"
TRANSLATE "were" "var"
TRANSLATE "done" "fullført"
BANNER Påminnelse for %w, %d. %m, %y%o:

View File

@@ -32,6 +32,7 @@ SET $Tomorrow "jutro"
TRANSLATE "yesterday" "wczoraj"
TRANSLATE "are" "są"
TRANSLATE "were" "byli"
TRANSLATE "done" "ukończone"
BANNER Terminarz na %w, %d. %m %y%o:

View File

@@ -32,6 +32,7 @@ SET $Tomorrow "amanhã"
TRANSLATE "yesterday" "ontem"
TRANSLATE "are" "são"
TRANSLATE "were" "eram"
TRANSLATE "done" "concluída"
BANNER Avisos para %w, %d de %m de %y%o:

View File

@@ -31,6 +31,7 @@ SET $Tomorrow "mâine"
TRANSLATE "yesterday" "ieri"
TRANSLATE "are" "sunt"
TRANSLATE "were" "au fost"
TRANSLATE "done" "finalizată"
BANNER Reamintiri pentru %w, %d %m %y%o:

View File

@@ -639,6 +639,10 @@ If the reminder is a TODO reminder, then \fIflag\fR will be the integer
If the reminder has a COMPLETE-THROUGH date, then this key will be present
and its value will be a string of the form YYYY-MM-DD.
.TP
.B max_overdue \fIn\fR
If the reminder has a MAX-OVERDUE clause, then this key will be present and
its value will be the integer argument to MAX-OVERDUE.
.TP
.B priority \fIn\fR
The priority of the reminder. Always present; if no PRIORITY keyword
is specified, then a reminder has a default priority of 5000.

View File

@@ -551,9 +551,13 @@ Do not issue TODO-type reminders.
.B \-\-json
In Agenda Mode, output JSON instead of the normal text-mode output.
\fB\-\-json\fR also disables sorting (the \fB-g\fR option) and
disables queueing (the \fB-q\fR option). See the section
"AGENDA MODE JSON OUTPUT" for more details. The \fB\-\-json\fR option
is ignored in Calendar Mode.
disables queueing (the \fB-q\fR option). See the section "AGENDA MODE
JSON OUTPUT" for more details. The \fB\-\-json\fR option is ignored
in Calendar Mode. Note that in JSON mode, the output from any
\fBRUN\fR-type reminder that would normally appear on standard output
is redirected to standard error instead; this is so that \fBRUN\fR-type
reminders don't mess up the output and cause invalid JSON to be produced
on standard output.
.TP
.B \-\-print-errs
The \fB\-\-print-errs\fR option causes \fBRemind\fR to print all
@@ -668,6 +672,7 @@ Its syntax is:
[\fIdelta\fR]
[\fIrepeat\fR]
[\fBTODO\fR]
[\fBMAX-OVERDUE\fR \fIn\fR]
[\fBCOMPLETE-THROUGH\fR \fIcomplete_date\fR]
[\fBPRIORITY\fR \fIprio\fR]
[\fBSKIP\fR | \fBBEFORE\fR | \fBAFTER\fR]
@@ -1821,13 +1826,13 @@ is replaced with "s" if the value produced by \fB%7\fR is not 1.
.TP
.B %!
is replaced with "is" if the current date and time is before the
trigger date and the \fBAT\fR time, or "was" if it is after. The %!
trigger date and the \fBAT\fR time, or "was" if it is after. The \fB%!\fR
sequence may be used in a non-timed reminder, in which case only dates
are compared.
.TP
.B %?
is replaced with "are" if the current date and time is before the
trigger date and the \fBAT\fR time, or "were" if it is after. The %!
trigger date and the \fBAT\fR time, or "were" if it is after. The \fB%?\fR
sequence may be used in a non-timed reminder, in which case only dates
are compared.
.TP
@@ -1837,9 +1842,17 @@ is similar to \fB%2\fR but displays the current time.
.B %#
is similar to \fB%3\fR but displays the current time.
.TP
.B %:
is replaced with " (done)" for a TODO reminder whose trigger date
is on or after its COMPLETE-THROUGH date. It is replaced with the empty
string in any other situation. Note that because \fBRemind\fR does not
display completed TODO reminders in Agenda Mode, this escape sequence
is really only useful in Calendar Mode.
.TP
.B
%"
(percent-doublequote - ") is removed. This sequence is not
.\" "
(percent-doublequote) is removed. This sequence is not
used by the substitution filter,
but is used to tell \fBRemind\fR which text to include in a calendar
entry when the \fB\-c\fR, \fB\-s\fR or \fB\-p\fR option is chosen.
@@ -1863,12 +1876,12 @@ sequences.
.TP
o
The a, c, e, f, g, h, i, j, k, l, u, v, 2, and 3 substitutions may
be preceded by an asterisk (for example, %*c) which causes the word
be preceded by an asterisk (for example, \fB%*c\fR) which causes the word
"at" or "on" that would normally be included in the output to be
omitted.
.TP
o
The ! and ? substitutions may be preceded by an asterisk (%*! or %*?),
The ! and ? substitutions may be preceded by an asterisk (\fB%*!\fR or \fB%*?\fR),
in which case the comparison is made between the trigger date and
realtoday() instead of today().
.TP
@@ -1944,7 +1957,7 @@ which recurrences have been completed. For example:
Canadian income taxes must be filed every 30 April. The above command
will remind you to pay taxes in 2026. If you don't update the
COMPLETE-THROUGH date, then after 30 April 2026, \fBRemind\fR will
keep reminding you until the end of time that my taxes were due on 30
keep reminding you until the end of time that your taxes were due on 30
April 2026. To indicate that you've paid them, simply update the
COMPLETE-THROUGH date to 2026-04-30 and then \fBRemind\fR will start
reminding you of your 2027 taxes (starting 15 days before the due
@@ -1952,6 +1965,26 @@ date.)
.PP
It is an error to specify COMPLETE-THROUGH without also specifying TODO.
.PP
.SH LIMITING REMINDERS ABOUT OVERDUE TODOS
.PP
Although \fBRemind\fR is happy to keep reminding you about overdue
TODOs for hundreds of years, for some things this may be pointless.
If you want \fBRemind\fR to \fIstop\fR nagging you about an overdue
TODO after a certain number of days, use the MAX-OVERDUE \fIn\fR clause.
In this case, \fBRemind\fR stops reminding you of a TODO that is overdue
by more than \fIn\fR days. Here is an example.
.PP
.nf
REM TODO 2025-08-13 ++5 MAX-OVERDUE 5 MSG Task: %b.
.fi
.PP
\fBRemind\fR \fIstarts\fR reminding you of the task on 2025-08-08,
because of the ++5 back value. It keeps reminding you of the task
after the due date. However, the last time it will remind you
will be on 2025-08-18, because of the MAX-OVERDUE clause. Starting
on 2025-08-19, \fBRemind\fR will no longer remind you of the task
since it's probably pointless---it has passed the MAX-OVERDUE period.
.PP
.SH MORE DETAILS ABOUT TODOS
.PP
TODOs are treated specially only in Agenda Mode. In Calendar Mode,
@@ -2969,6 +3002,10 @@ output is not a terminal, then the default is 72.If an \fBMSF\fR-type
reminder contains a word too long to fit in this width, it will not be
truncated - the width limit will be ignored.
.TP
.B $HideCompletedTodos (read-only)
If non-zero, then the \fB\-\-hide-completed-todos\fR option was supplied
on the command line.
.TP
.B $HushMode (read-only)
If non-zero, then the \fB\-h\fR option was supplied on the command line.
.TP
@@ -3056,6 +3093,9 @@ updates \fB$LongDeg\fR, \fB$LongMin\fR and \fB$LongSec\fR. Similar
rules apply to \fB$Latitude\fR, \fB$LatDeg\fR, \fB$LatMin\fR and \fB$LatSec\fR.
.RE
.TP
.B $JSONMode (read-only)
If non-zero, then the \fB\-\-json\fR command-line option was supplied.
.TP
.B $MaxLateMinutes
This variable controls how \fBRemind\fR reacts to a computer being suspended
and then woken. Normally, if a timed reminder is queued and then the
@@ -3259,6 +3299,11 @@ This variable returns 1 if the internal C \fBtime_t\fR type is at least
to represent dates after about 2038, and \fBRemind\fR will use a workaround to avoid
problems. See also the section "MACHINES WITH A 32-BIT TIME_T TYPE"
.TP
.B $TodoFilter (read-only)
If 0, then both events and TODOs are being output. If 1, then the
\fB\-\-only-todos\fR command-line option was supplied. If 2, then
the \fB\-\-only-events\fR command-line option was spplied.
.TP
.B $UntimedFirst (read-only)
Set to 1 if the \fB\-g\fR option is used with a fourth sort character
of "d"; set to 0 otherwise.
@@ -4742,6 +4787,18 @@ the empty string to variable b:
.PP
.RE
.TP
.B trigistodo()
Returns 1 if the last \fRREM\fR command was a \fBTODO\fR type or 0 if not.
.TP
.B trigcompletethrough()
Returns a \fBDATE\fR object that is the COMPLETE-THROUGH date of the most
recent \fBREM\fR command. If there was no COMPLETE-THROUGH date,
returns the \fBINT\fR 0.
.TP
.B trigmaxoverdue()
Returns an \fBINT\fR that is the MAX-OVERDUE value of the most recent \fBREM\fR
command. If there was no MAX-OVERDUE clause, returns -1.
.TP
.B trigger(d_date [,t_time [,i_utcflag]]) \fRor\fB trigger(q_datetime [,i_utcflag])
Returns a string suitable for use in a \fBREM\fR command or a
\fBSCANFROM\fR or UNTIL clause, allowing you to calculate trigger
@@ -6432,6 +6489,20 @@ as:
FSET subst_bx(a,d,t) iif(d==today()+2, "the day after tomorrow", 0)
.fi
.PP
You can override substitution sequences that are not alphanumeric as follows:
.RS
.PP
Override %: with \fBsubst_colon\fR
.PP
Override %! with \fBsubst_bang\fR
.PP
Override %? with \fBsubst_question\fR
.PP
Override %@ with \fBsubst_at\fR
.PP
Override %# with \fBsubst_hash\fR
.RE
.PP
You can define your own substitution sequences in addition to the built-in
ones as follows: If you define a function named \fBsubst_\fIname\fB(alt, date, time)\fR, then the sequence \fB%{name}\fR calls the function with \fBalt\fR
set to 0 and \fBdate\fR and \fBtime\fR to the trigger date and time,

View File

@@ -99,9 +99,11 @@ with the reminder. You can also specify advance notice, possibly
repeating.
The sixth control allows you to specify whether the reminder is a TODO,
and if so, its completion date. Double-clicking in the "Complete through"
and if so, its completion date. Double-clicking in the "Complete through:"
field automatically fills in the date of the calendar entry. Otherwise,
enter a possible completion date in the form YYYY-MM-DD.
enter a possible completion date in the form YYYY-MM-DD. You can also enter
a number in the "Max overdue days:" field to limit how many days past the
due date \fBRemind\fR will keep reminding you of the TODO.
The seventh control specifies what \fBRemind\fR should do if a reminder
falls on a holiday or weekend.

View File

@@ -1718,8 +1718,10 @@ proc toggle_complete_through { w } {
global todobut
if {$todobut} {
$w.complete_through configure -state normal
$w.max_overdue configure -state normal
} else {
$w.complete_through configure -state disabled
$w.max_overdue configure -state disabled
}
}
@@ -1962,7 +1964,7 @@ proc CreateModifyDialog {w day firstDay args} {
pack $w.durationbut $w.durationh $w.durationcolon $w.durationm -side left -anchor w -in $w.durationbox
# TODO?
checkbutton $w.todobut -text "This is a TODO " -command [list toggle_complete_through $w]
checkbutton $w.todobut -text "This is a TODO " -command [list toggle_complete_through $w]
balloon_add_help $w.todobut "Select if this is a TODO-type reminder"
$w.todobut deselect
@@ -1970,7 +1972,10 @@ proc CreateModifyDialog {w day firstDay args} {
entry $w.complete_through -width 20
bind $w.complete_through <Double-Button-1> [list complete_through_today $w]
balloon_add_help $w.complete_through "Enter the date of completed TODO in the form YYYY-MM-DD"
pack $w.todobut $w.lcomplete $w.complete_through -side left -anchor w -in $w.todobox
label $w.loverdue -text "Max overdue days: "
entry $w.max_overdue -width 6
balloon_add_help $w.max_overdue "Enter the maximum number of days Remind should nag you about an overdue TODO"
pack $w.todobut $w.lcomplete $w.complete_through $w.loverdue $w.max_overdue -side left -anchor w -in $w.todobox
# SKIP TYPE
label $w.labhol -text "On holidays or weekends:"
@@ -2079,6 +2084,7 @@ proc OptionsToRemindDialog { w opts } {
set hour ""
set ampm ""
$w.complete_through configure -state normal
$w.max_overdue configure -state normal
foreach {flag value} $opts {
switch -glob -- $flag {
"-text-*" {
@@ -2361,6 +2367,10 @@ proc CreateReminder {w} {
if {"$ct" != ""} {
append rem " COMPLETE-THROUGH $ct"
}
set mo [string trim [$w.max_overdue get]]
if {"$mo" != ""} {
append rem " MAX-OVERDUE $mo"
}
}
global SkipType
if {$SkipType == 2} {
@@ -3581,6 +3591,12 @@ proc ReadTaggedOptions { tag date } {
lappend ans -entry-complete_through ""
}
if {[dict exists $obj max_overdue]} {
lappend ans -entry-max_overdue [dict get $obj max_overdue]
} else {
lappend ans -entry-max_overdue ""
}
# Figure out the reminder type
if {[dict exists $obj rep]} {
# Repeat must be type 1
@@ -3756,7 +3772,7 @@ proc EditableEnter { w } {
if {"$c" != ""} {
$w tag configure $tag -underline 1
# underlinefg not supported on older versions of Tk
eval { $w tag configure $tag -underlinefg $c }
catch { $w tag configure $tag -underlinefg $c }
} else {
$w tag configure $tag -underline 1
}
@@ -4302,7 +4318,7 @@ proc DisplayTimeContinuously {} {
DisplayTime
# Reap any zombies
eval { exec true }
catch { exec true }
set secs [clock format [clock seconds] -format "%S"]
# Doh -- don't interpret as an octal number if leading zero
@@ -4372,7 +4388,7 @@ proc ShowTodaysReminders { force date } {
} else {
set date [clock format [clock seconds] -format "%Y-%m-%d" -locale C]
}
append cmdline " 2>@1"
append cmdline " 2>/dev/null"
set f [open $cmdline r]
while {[gets $f line] >= 0} {
append stuff "$line\n"

View File

@@ -2520,6 +2520,9 @@ void WriteJSONTrigger(Trigger const *t, int include_tags)
PrintJSONKeyPairInt("scanfrom", t->scanfrom);
}
}
if (t->max_overdue >= 0) {
PrintJSONKeyPairInt("max_overdue", t->max_overdue);
}
PrintJSONKeyPairDate("from", t->from);
PrintJSONKeyPairInt("priority", t->priority);
PrintJSONKeyPairDateTime("eventstart", t->eventstart);

View File

@@ -293,6 +293,12 @@ int DoRem(ParsePtr p)
return E_COMPLETE_WITHOUT_TODO;
}
if (trig.max_overdue >= 0 && !trig.is_todo) {
PurgeEchoLine("%s\n", CurLine);
FreeTrig(&trig);
return E_MAX_OVERDUE_WITHOUT_TODO;
}
if (trig.typ == NO_TYPE) {
if (!Hush) {
PurgeEchoLine("%s\n", "#!P! Cannot parse next line");
@@ -668,6 +674,7 @@ int ParseRem(ParsePtr s, Trigger *trig, TimeTrig *tim)
tim->duration = NO_TIME;
trig->need_wkday = 0;
trig->is_todo = 0;
trig->max_overdue = -1;
trig->complete_through = NO_DATE;
trig->adj_for_last = 0;
trig->infos = NULL;
@@ -758,6 +765,25 @@ int ParseRem(ParsePtr s, Trigger *trig, TimeTrig *tim)
trig->skip = tok.val;
break;
case T_MaxOverdue:
if (trig->max_overdue >= 0) return E_MAX_OVERDUE_TWICE;
DBufFree(&buf);
r = ParseToken(s, &buf);
if (r) return r;
FindToken(DBufValue(&buf), &tok);
DBufFree(&buf);
if (tok.type == T_Illegal) {
return -tok.val;
}
if (tok.type != T_Day && tok.type != T_Year && tok.type != T_Number) {
return E_EXPECTING_NUMBER;
}
if (tok.val < 0) {
return E_2LOW;
}
trig->max_overdue = tok.val;
break;
case T_Priority:
DBufFree(&buf);
r=ParsePriority(s, trig);
@@ -1664,8 +1690,14 @@ int ShouldTriggerReminder(Trigger const *t, TimeTrig const *tim, int dse, int *e
}
/* DO trigger if has not been completed through trigger date */
if (t->complete_through == NO_DATE || t->complete_through < dse) {
/* Trigger date is in the past - overdue */
/* Trigger date is in the past - overdue, Trigger unless we're
more than max_overdue days late */
if (dse < DSEToday) {
if (t->max_overdue >= 0) {
if (dse + t->max_overdue < DSEToday) {
return 0;
}
}
return 1;
}
/* Trigger date in future - use normal Remind rules */
@@ -1961,8 +1993,14 @@ static int ShouldTriggerBasedOnWarn(Trigger const *t, int dse, int *err)
}
/* DO trigger if has not been completed through trigger date */
if (t->complete_through == NO_DATE || t->complete_through < dse) {
/* Trigger date is in the past - overdue */
/* Trigger date is in the past - overdue, Trigger unless we're
more than max_overdue days late */
if (dse < DSEToday) {
if (t->max_overdue >= 0) {
if (dse + t->max_overdue < DSEToday) {
return 0;
}
}
return 1;
}
/* Trigger date in future - use normal Remind rules */

View File

@@ -31,6 +31,39 @@
#define SHIP_OUT(s) if(DBufPuts(dbuf, s) != OK) return E_NO_MEM
static char const *
get_function_override(int c, int addx)
{
static char func[32];
if (isalnum(c) || c == '_') {
if (addx) {
snprintf(func, sizeof(func), "subst_%cx", tolower(c));
} else {
snprintf(func, sizeof(func), "subst_%c", tolower(c));
}
return func;
}
if (addx) {
switch(c) {
case ':': return "subst_colonx";
case '!': return "subst_bangx";
case '?': return "subst_questionx";
case '@': return "subst_atx";
case '#': return "subst_hashx";
}
} else {
switch(c) {
case ':': return "subst_colon";
case '!': return "subst_bang";
case '?': return "subst_question";
case '@': return "subst_at";
case '#': return "subst_hash";
}
}
return NULL;
}
static int
check_subst_args(UserFunc *f, int n)
{
@@ -75,7 +108,7 @@ int DoSubst(ParsePtr p, DynamicBuffer *dbuf, Trigger *t, TimeTrig const *tt, int
char const *expr;
char *os;
char s[256];
char uf[32];
char const *substname;
char mypm[64];
char mycpm[64];
char myplu[64];
@@ -340,11 +373,15 @@ int DoSubst(ParsePtr p, DynamicBuffer *dbuf, Trigger *t, TimeTrig const *tt, int
continue;
}
done = 0;
snprintf(uf, sizeof(uf), "subst_%c", tolower(c));
func = FindUserFunc(uf);
substname = get_function_override(c, 0);
if (substname) {
func = FindUserFunc(substname);
} else {
func = NULL;
}
if (func && check_subst_args(func, 3)) {
snprintf(s, sizeof(s), "subst_%c(%d,'%04d-%02d-%02d',%02d:%02d)",
tolower(c), altmode ? 1 : 0, y, m+1, d, h, min);
snprintf(s, sizeof(s), "%s(%d,'%04d-%02d-%02d',%02d:%02d)",
substname, altmode ? 1 : 0, y, m+1, d, h, min);
expr = (char const *) s;
r = EvalExpr(&expr, &v, NULL);
if (r == OK) {
@@ -392,11 +429,15 @@ int DoSubst(ParsePtr p, DynamicBuffer *dbuf, Trigger *t, TimeTrig const *tt, int
if (!done) {
snprintf(uf, sizeof(uf), "subst_%cx", tolower(c));
func = FindUserFunc(uf);
substname = get_function_override(c, 1);
if (substname) {
func = FindUserFunc(substname);
} else {
func = NULL;
}
if (func && check_subst_args(func, 3)) {
snprintf(s, sizeof(s), "subst_%cx(%d,'%04d-%02d-%02d',%02d:%02d)",
tolower(c), altmode ? 1 : 0, y, m+1, d, h, min);
snprintf(s, sizeof(s), "%s(%d,'%04d-%02d-%02d',%02d:%02d)",
substname, altmode ? 1 : 0, y, m+1, d, h, min);
expr = (char const *) s;
r = EvalExpr(&expr, &v, NULL);
if (r == OK) {
@@ -617,6 +658,12 @@ int DoSubst(ParsePtr p, DynamicBuffer *dbuf, Trigger *t, TimeTrig const *tt, int
SHIP_OUT(s);
break;
case ':':
if (t->is_todo && t->complete_through != NO_DATE && t->complete_through >= dse) {
snprintf(s, sizeof(s), " (%s)", tr("done"));
SHIP_OUT(s);
}
break;
case '1':
if (tdiff == 0)
snprintf(s, sizeof(s), "%s", tr("now"));

View File

@@ -130,6 +130,8 @@
#define E_EXPR_DISABLED 106
#define E_TIME_EXCEEDED 107
#define E_COMPLETE_WITHOUT_TODO 108
#define E_MAX_OVERDUE_TWICE 109
#define E_MAX_OVERDUE_WITHOUT_TODO 110
#ifdef MK_GLOBALS
#undef EXTERN
@@ -257,6 +259,8 @@ EXTERN char *ErrMsg[]
/* E_EXPR_DISABLED */ "Expression evaluation is disabled",
/* E_TIME_EXCEEDED */ "Time limit for expression evaluation exceeded",
/* E_COMPLETE_WITHOUT_TODO */ "COMPLETE-THROUGH specified without TODO",
/* E_MAX_OVERDUE_TWICE */ "MAX-OVERDUE specified twice",
/* E_MAX_OVERDUE_WITHOUT_TODO */ "MAX-OVERDUE specified without TODO",
}
#endif /* MK_GLOBALS */
;

View File

@@ -179,17 +179,14 @@ got_a_fresh_line(void)
WarnedAboutImplicit = 0;
}
static void set_cloexec(FILE *fp)
void set_cloexec(int fd)
{
int flags;
int fd;
if (fp) {
fd = fileno(fp);
flags = fcntl(fd, F_GETFD);
if (flags >= 0) {
flags |= FD_CLOEXEC;
fcntl(fd, F_SETFD, flags);
}
flags = fcntl(fd, F_GETFD);
if (flags >= 0) {
flags |= FD_CLOEXEC;
fcntl(fd, F_SETFD, flags);
}
}
@@ -218,7 +215,7 @@ static void OpenPurgeFile(char const *fname, char const *mode)
fprintf(ErrFp, tr("Cannot open `%s' for writing: %s"), DBufValue(&fname_buf), strerror(errno));
fprintf(ErrFp, "\n");
}
set_cloexec(PurgeFP);
set_cloexec(fileno(PurgeFP));
DBufFree(&fname_buf);
}
@@ -427,7 +424,9 @@ static int OpenFile(char const *fname)
}
} else {
fp = fopen(fname, "r");
set_cloexec(fp);
if (fp) {
set_cloexec(fileno(fp));
}
if (DebugFlag & DB_TRACE_FILES) {
fprintf(ErrFp, tr("Reading `%s': Opening file on disk"), fname);
fprintf(ErrFp, "\n");
@@ -449,7 +448,7 @@ static int OpenFile(char const *fname)
if (strcmp(fname, "-")) {
fp = fopen(fname, "r");
if (!fp || !CheckSafety()) return E_CANT_OPEN;
set_cloexec(fp);
set_cloexec(fileno(fp));
if (PurgeMode) OpenPurgeFile(fname, "w");
} else {
fp = stdin;
@@ -650,7 +649,7 @@ static int PopFile(void)
if (strcmp(i->filename, "-")) {
fp = fopen(i->filename, "r");
if (!fp || !CheckSafety()) return E_CANT_OPEN;
set_cloexec(fp);
set_cloexec(fileno(fp));
if (PurgeMode) OpenPurgeFile(i->filename, "a");
} else {
fp = stdin;

View File

@@ -173,6 +173,7 @@ static int FToday (func_info *);
static int FTrig (func_info *);
static int FTrigback (func_info *);
static int FTrigbase (func_info *info);
static int FTrigcompletethrough (func_info *);
static int FTrigdate (func_info *);
static int FTrigdatetime (func_info *);
static int FTrigdelta (func_info *);
@@ -182,6 +183,8 @@ static int FTrigeventduration(func_info *);
static int FTrigeventstart (func_info *);
static int FTrigfrom (func_info *);
static int FTrigger (func_info *);
static int FTrigistodo (func_info *);
static int FTrigmaxoverdue (func_info *);
static int FTrigpriority (func_info *);
static int FTrigrep (func_info *);
static int FTrigscanfrom (func_info *);
@@ -347,6 +350,7 @@ BuiltinFunc Func[] = {
{ "trig", 0, NO_MAX, 0, FTrig, NULL },
{ "trigback", 0, 0, 0, FTrigback, NULL },
{ "trigbase", 0, 0, 0, FTrigbase, NULL },
{ "trigcompletethrough", 0, 0, 0, FTrigcompletethrough, NULL },
{ "trigdate", 0, 0, 0, FTrigdate, NULL },
{ "trigdatetime", 0, 0, 0, FTrigdatetime, NULL },
{ "trigdelta", 0, 0, 0, FTrigdelta, NULL },
@@ -356,6 +360,8 @@ BuiltinFunc Func[] = {
{ "trigfrom", 0, 0, 0, FTrigfrom, NULL },
{ "trigger", 1, 3, 0, FTrigger, NULL },
{ "triginfo", 1, 1, 0, FTriginfo, NULL },
{ "trigistodo", 0, 0, 0, FTrigistodo, NULL },
{ "trigmaxoverdue", 0, 0, 0, FTrigmaxoverdue, NULL },
{ "trigpriority", 0, 0, 0, FTrigpriority, NULL },
{ "trigrep", 0, 0, 0, FTrigrep, NULL },
{ "trigscanfrom", 0, 0, 0, FTrigscanfrom, NULL },
@@ -1897,6 +1903,32 @@ static int FTrigback(func_info *info)
return OK;
}
static int FTrigcompletethrough(func_info *info)
{
if (!LastTrigger.is_todo || LastTrigger.complete_through == NO_DATE) {
RetVal.type = INT_TYPE;
RETVAL = 0;
return OK;
}
RetVal.type = DATE_TYPE;
RETVAL = LastTrigger.complete_through;
return OK;
}
static int FTrigistodo(func_info *info)
{
RetVal.type = INT_TYPE;
RETVAL = LastTrigger.is_todo;
return OK;
}
static int FTrigmaxoverdue(func_info *info)
{
RetVal.type = INT_TYPE;
RETVAL = LastTrigger.max_overdue;
return OK;
}
static int FTrigdelta(func_info *info)
{
RetVal.type = INT_TYPE;

View File

@@ -803,6 +803,8 @@ void InitRemind(int argc, char const *argv[])
SortByTime = SORT_NONE;
SortByDate = SORT_NONE;
SortByPrio = SORT_NONE;
/* Make sure we don't blat errors to stdout! */
ErrFp = stderr;
}
/* Figure out the offset from UTC */
@@ -1149,10 +1151,18 @@ ProcessLongOption(char const *arg)
return;
}
if (!strcmp(arg, "only-todos")) {
if (TodoFilter == ONLY_EVENTS) {
fprintf(ErrFp, "remind: Cannot combine --only-todos and --only-events\n");
exit(1);
}
TodoFilter = ONLY_TODOS;
return;
}
if (!strcmp(arg, "only-events")) {
if (TodoFilter == ONLY_TODOS) {
fprintf(ErrFp, "remind: Cannot combine --only-todos and --only-events\n");
exit(1);
}
TodoFilter = ONLY_EVENTS;
return;
}

View File

@@ -1576,6 +1576,15 @@ void DoExit(ParsePtr p)
if (PurgeMode) return;
if (JSONMode) {
if (JSONLinesEmitted) {
printf("}\n");
}
/* Close the reminder list */
printf("]\n");
}
fflush(stdout);
fflush(stderr);
r = EvaluateExpr(p, &v);
if (r || v.type != INT_TYPE) exit(99);
exit(v.v.val);
@@ -2056,11 +2065,15 @@ ClearLastTriggers(void)
LastTrigger.warn[0] = 0;
LastTrigger.omitfunc[0] = 0;
LastTrigger.passthru[0] = 0;
LastTrigger.is_todo = 0;
LastTrigger.complete_through = NO_DATE;
LastTrigger.max_overdue = -1;
FreeTrig(&LastTrigger);
LastTimeTrig.ttime = NO_TIME;
LastTimeTrig.delta = NO_DELTA;
LastTimeTrig.rep = NO_REP;
LastTimeTrig.duration = NO_TIME;
}
void
@@ -2137,7 +2150,12 @@ System(char const *cmd, int is_queued)
}
}
/* This is the child process or original if we never forked */
(void) system(cmd);
if (JSONMode) {
(void) system_to_stderr(cmd);
} else {
(void) system(cmd);
}
if (do_exit) {
/* In the child process, so exit! */
exit(0);

View File

@@ -512,6 +512,14 @@ DumpOmits(void)
{
int i;
int y, m, d;
/* Do nothing in --json mode */
if (JSONMode) {
return;
}
if (PurgeMode) {
return;
}
printf("Global Full OMITs (%d of maximum allowed %d):\n", NumFullOmits, MAX_FULL_OMITS);
if (!NumFullOmits) {
printf("\tNone.\n");

View File

@@ -292,3 +292,5 @@ void SetCurrentFilename(char const *fname);
char const *GetCurrentFilename(void);
int get_scanfrom(Trigger const *t);
void remove_trailing_newlines(DynamicBuffer *buf);
void set_cloexec(int fd);
int system_to_stderr(char const *cmd);

View File

@@ -80,6 +80,7 @@ Token TokArray[] = {
{ "lastday", 7, T_BackAdj, -1 },
{ "lastworkday", 11, T_BackAdj, 1 },
{ "march", 3, T_Month, 2 },
{ "max-overdue", 11, T_MaxOverdue, 0 },
{ "may", 3, T_Month, 4 },
{ "maybe-uncomputable", 5, T_MaybeUncomputable, 0},
{ "monday", 3, T_WkDay, 0 },

View File

@@ -16,6 +16,7 @@
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <stddef.h>
#include <stdlib.h>
@@ -63,6 +64,9 @@ GenerateTranslationTemplate(void)
{
int i;
if (JSONMode) {
return;
}
printf("# Translation table template\n\n");
printf("TRANSLATE \"LANGID\" ");
@@ -233,6 +237,10 @@ DumpTranslationTable(FILE *fp, int json)
XlateItem *item;
int done = 0;
char const *t;
if (fileno(fp) == STDOUT_FILENO && JSONMode) {
return;
}
if (!json) {
fprintf(fp, "# Translation table\n");
/* Always to LANGID first */

View File

@@ -145,6 +145,7 @@ typedef struct {
int maybe_uncomputable; /* Suppress "can't compute trigger" warnings */
int addomit; /* Add trigger date to global OMITs */
int noqueue; /* Don't queue even if timed */
int max_overdue; /* Stop warning if TODO is too far overdue */
char sched[VAR_NAME_LEN+1]; /* Scheduling function */
char warn[VAR_NAME_LEN+1]; /* Warning function */
char omitfunc[VAR_NAME_LEN+1]; /* OMITFUNC function */
@@ -234,7 +235,7 @@ enum TokTypes
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_Info, T_LastBack,
T_LongTime, T_MaybeUncomputable, T_Month, T_NoQueue, T_Number,
T_LongTime, T_MaxOverdue, T_MaybeUncomputable, T_Month, T_NoQueue, T_Number,
T_Omit, T_OmitFunc, T_Once, T_Ordinal, T_Pop, T_PopFuncs, T_PopVars,
T_Preserve, T_Priority, T_Push, T_PushFuncs, T_PushVars, T_Rem,
T_RemType, T_Rep, T_Return, T_Scanfrom, T_Sched, T_Set, T_Skip, T_Tag,

View File

@@ -17,6 +17,7 @@ static char const DontEscapeMe[] =
#include "err.h"
#include <string.h>
#include <unistd.h>
#ifdef HAVE_STRINGS_H
#include <strings.h>
@@ -30,6 +31,43 @@ static char const DontEscapeMe[] =
#include "globals.h"
#include "protos.h"
/***************************************************************/
/* */
/* system_to_stderr */
/* */
/* Run system(...) but with stdout redirected to stderr */
/* */
/***************************************************************/
int system_to_stderr(char const *cmd)
{
int stdout_dup = dup(STDOUT_FILENO);
int r;
if (stdout_dup < 0) {
perror("dup");
return -1;
}
/* Duplicate STDERR onto STDOUT */
if (dup2(STDERR_FILENO, STDOUT_FILENO) < 0) {
(void) close(stdout_dup);
return -1;
}
/* Set close-on-exec flag on stdout_dup */
set_cloexec(stdout_dup);
r = system(cmd);
/* Restore original stdout */
/* If this dup2 fails... there's not a whole lot we can do. */
(void) dup2(stdout_dup, STDOUT_FILENO);
if (STDOUT_FILENO != stdout_dup) {
(void) close(stdout_dup);
}
return r;
}
/***************************************************************/
/* */
/* StrnCpy */

View File

@@ -761,6 +761,7 @@ int DoDump(ParsePtr p)
int dump_constness = 0;
if (PurgeMode) return OK;
if (JSONMode) return OK;
DBufInit(&buf);
r = ParseToken(p, &buf);
@@ -992,6 +993,7 @@ static SysVar SysVarArr[] = {
{"FormWidth", 1, INT_TYPE, &FormWidth, 20, 500 },
{"Friday", 1, TRANS_TYPE, "Friday", 0, 0 },
{"Fromnow", 1, TRANS_TYPE, "from now", 0, 0 },
{"HideCompletedTodos", 0, INT_TYPE, &HideCompletedTodos, 0, 0 },
{"Hour", 1, TRANS_TYPE, "hour", 0, 0 },
{"Hplu", 1, STR_TYPE, &DynamicHplu, 0, 0 },
{"HushMode", 0, INT_TYPE, &Hush, 0, 0 },
@@ -1001,6 +1003,7 @@ static SysVar SysVarArr[] = {
{"IntMin", 0, INT_TYPE, &IntMin, 0, 0 },
{"Is", 1, TRANS_TYPE, "is", 0, 0 },
{"January", 1, TRANS_TYPE, "January", 0, 0 },
{"JSONMode", 0, INT_TYPE, &JSONMode, 0, 0 },
{"July", 1, TRANS_TYPE, "July", 0, 0 },
{"June", 1, TRANS_TYPE, "June", 0, 0 },
{"LatDeg", 1, SPECIAL_TYPE, latdeg_func, 0, 0 },
@@ -1058,6 +1061,7 @@ static SysVar SysVarArr[] = {
{"TimetIs64bit", 0, SPECIAL_TYPE, timet_is_64_func, 0, 0 },
{"Tm", 0, SPECIAL_TYPE, trig_mon_func, 0, 0 },
{"Today", 1, TRANS_TYPE, "today", 0, 0 },
{"TodoFilter", 0, INT_TYPE, &TodoFilter, 0, 0 },
{"Tomorrow", 1, TRANS_TYPE, "tomorrow", 0, 0 },
{"Tt", 0, SPECIAL_TYPE, trig_time_func, 0, 0 },
{"Tu", 0, SPECIAL_TYPE, trig_until_func, 0, 0 },

5
tests/json-redirect.rem Normal file
View File

@@ -0,0 +1,5 @@
BANNER %
SET $AddBlankLines 0
REM MSG Hello
REM RUN echo This is executed by the shell.
REM MSG Goodbye

View File

@@ -769,6 +769,24 @@ echo "Testing TODOS in calendar mode" >> ../tests/test.out 2>&1
echo "" >> ../tests/test.out 2>&1
echo "Testing TODOS in calendar mode with completed todos hidden" >> ../tests/test.out 2>&1
../src/remind -s --hide-completed-todos ../tests/todos.rem 2025-08-13 >> ../tests/test.out 2>&1
echo "Testing TODOS and JSON mode" >> ../tests/test.out 2>&1
../src/remind --json ../tests/todos.rem 2025-08-13 >> ../tests/test.out 2>&1
echo "Testing proper redirection of RUN stdout in JSON mode... here's stdout" >> ../tests/test.out 2>&1
../src/remind --json ../tests/json-redirect.rem 1 Jan 2025 >> ../tests/test.out 2>/dev/null
echo "... and here is stderr" >> ../tests/test.out 2>&1
../src/remind --json ../tests/json-redirect.rem 1 Jan 2025 > /dev/null 2>> ../tests/test.out
# Test %: substitution sequence in all the languages
for i in ../include/lang/??.rem ; do
../src/remind "-ii=\"$i\"" -p - 2025-08-13 <<'EOF' 2>&1 | grep 2025/ >> ../tests/test.out
DO [i]
REM TODO 2025-08-13 MSG %(LANGID) Task1%:
REM TODO 2025-08-13 COMPLETE-THROUGH 2025-08-12 MSG %(LANGID) Task2%:
REM TODO 2025-08-13 COMPLETE-THROUGH 2025-08-13 MSG %(LANGID) Task3%:
EOF
done
cmp -s ../tests/test.out ../tests/test.cmp
if [ "$?" = "0" ]; then

View File

@@ -16580,12 +16580,13 @@ Entering UserFN c()
Leaving UserFN c() => 33
DEBUG -xe
Overridden: subst_colon subst_bang subst_question subst_at subst_hash
Variable hash table statistics:
Entries: 100144; Buckets: 87719; Non-empty Buckets: 66301
Maxlen: 5; Minlen: 0; Avglen: 1.142; Stddev: 0.878; Avg nonempty len: 1.510
Growths: 13; Shrinks: 0
Function hash table statistics:
Entries: 100018; Buckets: 87719; Non-empty Buckets: 63574
Entries: 100023; Buckets: 87719; Non-empty Buckets: 63576
Maxlen: 5; Minlen: 0; Avglen: 1.140; Stddev: 0.934; Avg nonempty len: 1.573
Growths: 13; Shrinks: 0
Dedupe hash table statistics:
@@ -24430,6 +24431,7 @@ info
last
lastday
lastworkday
max-overdue
maybe
maybe-uncomputable
msf
@@ -24618,6 +24620,7 @@ today
trig
trigback
trigbase
trigcompletethrough
trigdate
trigdatetime
trigdelta
@@ -24627,6 +24630,8 @@ trigeventstart
trigfrom
trigger
triginfo
trigistodo
trigmaxoverdue
trigpriority
trigrep
trigscanfrom
@@ -24681,6 +24686,7 @@ $FoldYear
$FormWidth
$Friday
$Fromnow
$HideCompletedTodos
$Hour
$Hplu
$HushMode
@@ -24690,6 +24696,7 @@ $IntMax
$IntMin
$Is
$January
$JSONMode
$July
$June
$LatDeg
@@ -24747,6 +24754,7 @@ $TimeSep
$TimetIs64bit
$Tm
$Today
$TodoFilter
$Tomorrow
$Tt
$Tu
@@ -24962,6 +24970,8 @@ TRANSLATE "Duplicate argument name" ""
TRANSLATE "Expression evaluation is disabled" ""
TRANSLATE "Time limit for expression evaluation exceeded" ""
TRANSLATE "COMPLETE-THROUGH specified without TODO" ""
TRANSLATE "MAX-OVERDUE specified twice" ""
TRANSLATE "MAX-OVERDUE specified without TODO" ""
# Other Messages
TRANSLATE "%s function `%s' defined at %s(%s) does not use its argument" ""
@@ -25044,6 +25054,7 @@ TRANSLATE "`%s' re-SET without being used (previous SET: %s:%d)" ""
TRANSLATE "are" ""
TRANSLATE "defined at" ""
TRANSLATE "did you mean" ""
TRANSLATE "done" ""
TRANSLATE "here" ""
TRANSLATE "psmoon() is deprecated; use SPECIAL MOON instead." ""
TRANSLATE "psshade() is deprecated; use SPECIAL SHADE instead." ""
@@ -39542,6 +39553,12 @@ On odd days
2000/06/30 * * * * On days divisible by 5
Testing TODOS in agenda mode
trigistodo() => 1
trigcompletethrough() => 2024-12-31
trigmaxoverdue() => 3
trigistodo() => 0
trigcompletethrough() => 0
trigmaxoverdue() => -1
First on 2025-08-01
Second on 2022-08-01
Third on 2023-08-01
@@ -39550,6 +39567,10 @@ Fifth today
Sixth on 2025-08-06
Eighth on 2025-08-20
Ninth today
Yup today
Yup2 yesterday
Yup3 on 2025-08-11
Yup4 on 2025-08-10
Testing TODOS in calendar mode
2025/08/01 * * * * First
@@ -39559,9 +39580,14 @@ Testing TODOS in calendar mode
2025/08/06 * * * * Seventh
2025/08/06 * * * * Eighth
2025/08/06 * * * * Ninth
2025/08/09 * * * * Nope
2025/08/10 * * * * Yup4
2025/08/11 * * * * Yup3
2025/08/12 * * * * Yup2
2025/08/13 * * * * Fifth
2025/08/13 * * * * Eighth
2025/08/13 * * * * Ninth
2025/08/13 * * * * Yup
2025/08/20 * * * * Fourth
2025/08/20 * * * * Eighth
2025/08/20 * * * * Ninth
@@ -39573,10 +39599,82 @@ Testing TODOS in calendar mode with completed todos hidden
2025/08/01 * * * * Second
2025/08/01 * * * * Third
2025/08/06 * * * * Sixth
2025/08/09 * * * * Nope
2025/08/10 * * * * Yup4
2025/08/11 * * * * Yup3
2025/08/12 * * * * Yup2
2025/08/13 * * * * Fifth
2025/08/13 * * * * Ninth
2025/08/13 * * * * Yup
2025/08/20 * * * * Fourth
2025/08/20 * * * * Eighth
2025/08/20 * * * * Ninth
2025/08/27 * * * * Eighth
2025/08/27 * * * * Ninth
Testing TODOS and JSON mode
[
{"date":"2025-08-01","filename":"../tests/todos.rem","lineno":4,"d":1,"m":8,"y":2025,"is_todo":1,"trigbase":"2025-08-1 ","priority":5000,"body":"First on 2025-08-01"},
{"date":"2022-08-01","filename":"../tests/todos.rem","lineno":5,"d":1,"m":8,"is_todo":1,"complete_through":"2022-07-31","priority":5000,"body":"Second on 2022-08-01"},
{"date":"2023-08-01","filename":"../tests/todos.rem","lineno":6,"d":1,"m":8,"is_todo":1,"complete_through":"2022-08-01","priority":5000,"body":"Third on 2023-08-01"},
{"date":"2025-08-20","filename":"../tests/todos.rem","lineno":7,"d":20,"m":8,"y":2025,"is_todo":1,"delta":7,"trigbase":"2025-08-20","priority":5000,"body":"Fourth on 2025-08-20"},
{"date":"2025-08-13","filename":"../tests/todos.rem","lineno":8,"d":13,"m":8,"y":2025,"is_todo":1,"delta":7,"trigbase":"2025-08-13","priority":5000,"body":"Fifth today"},
{"date":"2025-08-06","filename":"../tests/todos.rem","lineno":9,"d":6,"m":8,"y":2025,"is_todo":1,"delta":7,"trigbase":"2025-08-6 ","priority":5000,"body":"Sixth on 2025-08-06"},
{"date":"2025-08-20","filename":"../tests/todos.rem","lineno":11,"wd":["Wednesday"],"is_todo":1,"complete_through":"2025-08-13","delta":7,"priority":5000,"body":"Eighth on 2025-08-20"},
{"date":"2025-08-13","filename":"../tests/todos.rem","lineno":12,"wd":["Wednesday"],"is_todo":1,"complete_through":"2025-08-12","delta":7,"priority":5000,"body":"Ninth today"},
{"date":"2025-08-13","filename":"../tests/todos.rem","lineno":15,"d":13,"m":8,"y":2025,"is_todo":1,"trigbase":"2025-08-13","max_overdue":3,"priority":5000,"body":"Yup today"},
{"date":"2025-08-12","filename":"../tests/todos.rem","lineno":16,"d":12,"m":8,"y":2025,"is_todo":1,"trigbase":"2025-08-12","max_overdue":3,"priority":5000,"body":"Yup2 yesterday"},
{"date":"2025-08-11","filename":"../tests/todos.rem","lineno":17,"d":11,"m":8,"y":2025,"is_todo":1,"trigbase":"2025-08-11","max_overdue":3,"priority":5000,"body":"Yup3 on 2025-08-11"},
{"date":"2025-08-10","filename":"../tests/todos.rem","lineno":18,"d":10,"m":8,"y":2025,"is_todo":1,"trigbase":"2025-08-10","max_overdue":3,"priority":5000,"body":"Yup4 on 2025-08-10"}
]
Testing proper redirection of RUN stdout in JSON mode... here's stdout
[
{"date":"2025-01-01","filename":"../tests/json-redirect.rem","lineno":3,"is_todo":0,"priority":5000,"body":"Hello"},
{"date":"2025-01-01","filename":"../tests/json-redirect.rem","lineno":5,"is_todo":0,"priority":5000,"body":"Goodbye"}
]
... and here is stderr
This is executed by the shell.
2025/08/13 * * * * ca Task1
2025/08/13 * * * * ca Task2
2025/08/13 * * * * ca Task3 (completada)
2025/08/13 * * * * da Task1
2025/08/13 * * * * da Task2
2025/08/13 * * * * da Task3 (fuldført)
2025/08/13 * * * * de Task1
2025/08/13 * * * * de Task2
2025/08/13 * * * * de Task3 (abgeschlossen)
2025/08/13 * * * * en Task1
2025/08/13 * * * * en Task2
2025/08/13 * * * * en Task3 (done)
2025/08/13 * * * * es Task1
2025/08/13 * * * * es Task2
2025/08/13 * * * * es Task3 (completada)
2025/08/13 * * * * fi Task1
2025/08/13 * * * * fi Task2
2025/08/13 * * * * fi Task3 (suoritettu)
2025/08/13 * * * * fr Task1
2025/08/13 * * * * fr Task2
2025/08/13 * * * * fr Task3 (accomplie)
2025/08/13 * * * * gr Task1
2025/08/13 * * * * gr Task2
2025/08/13 * * * * gr Task3 (ολοκληρώθηκε)
2025/08/13 * * * * is Task1
2025/08/13 * * * * is Task2
2025/08/13 * * * * is Task3 (lokið)
2025/08/13 * * * * it Task1
2025/08/13 * * * * it Task2
2025/08/13 * * * * it Task3 (completato)
2025/08/13 * * * * nl Task1
2025/08/13 * * * * nl Task2
2025/08/13 * * * * nl Task3 (voltooid)
2025/08/13 * * * * no Task1
2025/08/13 * * * * no Task2
2025/08/13 * * * * no Task3 (fullført)
2025/08/13 * * * * pl Task1
2025/08/13 * * * * pl Task2
2025/08/13 * * * * pl Task3 (ukończone)
2025/08/13 * * * * pt Task1
2025/08/13 * * * * pt Task2
2025/08/13 * * * * pt Task3 (concluída)
2025/08/13 * * * * ro Task1
2025/08/13 * * * * ro Task2
2025/08/13 * * * * ro Task3 (finalizată)

View File

@@ -1707,6 +1707,15 @@ set a c()
DEBUG -xe
fset subst_colon(a, b, c) "subst_colon"
fset subst_bang(a, b, c) "subst_bang"
fset subst_question(a, b, c) "subst_question"
fset subst_at(a, b, c) "subst_at"
fset subst_hash(a, b, c) "subst_hash"
REM MSG Overridden: %: %! %? %@ %#
# Don't want Remind to queue reminders
EXIT

View File

@@ -10,3 +10,23 @@ REM TODO 6 Aug 2025 +7 MSG %"Sixth%" %l
REM TODO COMPLETE-THROUGH 2025-08-06 6 Aug 2025 +7 MSG %"Seventh%" %l
REM TODO Wed +7 COMPLETE-THROUGH 2025-08-13 MSG %"Eighth%" %l
REM TODO Wed +7 COMPLETE-THROUGH 2025-08-12 MSG %"Ninth%" %l
# Test MAX-OVERDUE
REM TODO 2025-08-13 MAX-OVERDUE 3 MSG %"Yup%" %l
REM TODO 2025-08-12 MAX-OVERDUE 3 MSG %"Yup2%" %l
REM TODO 2025-08-11 MAX-OVERDUE 3 MSG %"Yup3%" %l
REM TODO 2025-08-10 MAX-OVERDUE 3 MSG %"Yup4%" %l
REM TODO 2025-08-9 MAX-OVERDUE 3 MSG %"Nope%" %l
IF !$JSONMode
REM TODO 2025-09-09 COMPLETE-THROUGH 2024-12-31 MAX-OVERDUE 3 MSG %"Nope%" %l
debug +x
set a trigistodo()
set a trigcompletethrough()
set a trigmaxoverdue()
REM 2025-09-09 MSG blork
set a trigistodo()
set a trigcompletethrough()
set a trigmaxoverdue()
debug -x
ENDIF