mirror of
https://salsa.debian.org/dskoll/remind.git
synced 2026-04-16 06:18:47 +02:00
1653 lines
45 KiB
C
1653 lines
45 KiB
C
/***************************************************************/
|
|
/* */
|
|
/* MAIN.C */
|
|
/* */
|
|
/* Main program loop, as well as miscellaneous conversion */
|
|
/* routines, etc. */
|
|
/* */
|
|
/* This file is part of REMIND. */
|
|
/* Copyright (C) 1992-2022 by Dianne Skoll */
|
|
/* */
|
|
/***************************************************************/
|
|
|
|
#define _XOPEN_SOURCE 600
|
|
#include "config.h"
|
|
|
|
#include <errno.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <stdio.h>
|
|
#include <signal.h>
|
|
#include <string.h>
|
|
#include <stdarg.h>
|
|
#ifdef HAVE_LOCALE_H
|
|
#include <locale.h>
|
|
#endif
|
|
|
|
#include <ctype.h>
|
|
#ifdef TIME_WITH_SYS_TIME
|
|
#include <time.h>
|
|
#include <sys/time.h>
|
|
#else
|
|
#if defined(HAVE_SYS_TIME_H) || defined (TIME_WITH_SYS_TIME)
|
|
#include <sys/time.h>
|
|
#else
|
|
#include <time.h>
|
|
#endif
|
|
#endif
|
|
|
|
#include <sys/types.h>
|
|
#ifdef REM_USE_WCHAR
|
|
#include <wctype.h>
|
|
#include <wchar.h>
|
|
#endif
|
|
|
|
#include "types.h"
|
|
#include "protos.h"
|
|
#include "expr.h"
|
|
#include "globals.h"
|
|
#include "err.h"
|
|
|
|
static void DoReminders(void);
|
|
|
|
/***************************************************************/
|
|
/***************************************************************/
|
|
/** **/
|
|
/** Main Program Loop **/
|
|
/** **/
|
|
/***************************************************************/
|
|
/***************************************************************/
|
|
int main(int argc, char *argv[])
|
|
{
|
|
int pid;
|
|
|
|
#ifdef HAVE_SETLOCALE
|
|
setlocale(LC_ALL, "");
|
|
#endif
|
|
|
|
/* The very first thing to do is to set up ErrFp to be stderr */
|
|
ErrFp = stderr;
|
|
|
|
/* Set up global vars */
|
|
ArgC = argc;
|
|
ArgV = (char const **) argv;
|
|
|
|
InitRemind(argc, (char const **) argv);
|
|
DBufInit(&(LastTrigger.tags));
|
|
ClearLastTriggers();
|
|
|
|
if (DoCalendar || (DoSimpleCalendar && (!NextMode || PsCal))) {
|
|
ProduceCalendar();
|
|
return 0;
|
|
}
|
|
|
|
/* Are we purging old reminders? Then just run through the loop once! */
|
|
if (PurgeMode) {
|
|
DoReminders();
|
|
return 0;
|
|
}
|
|
|
|
/* Not doing a calendar. Do the regular remind loop */
|
|
ShouldCache = (Iterations > 1);
|
|
|
|
while (Iterations--) {
|
|
DoReminders();
|
|
|
|
if (DebugFlag & DB_DUMP_VARS) {
|
|
DumpVarTable();
|
|
DumpSysVarByName(NULL);
|
|
}
|
|
|
|
if (!Hush) {
|
|
if (DestroyOmitContexts())
|
|
Eprint("%s", ErrMsg[E_PUSH_NOPOP]);
|
|
if (!Daemon && !NextMode && !NumTriggered && !NumQueued) {
|
|
printf("%s\n", ErrMsg[E_NOREMINDERS]);
|
|
} else if (!Daemon && !NextMode && !NumTriggered) {
|
|
printf(ErrMsg[M_QUEUED], NumQueued);
|
|
}
|
|
}
|
|
|
|
/* If there are sorted reminders, handle them */
|
|
if (SortByDate) IssueSortedReminders();
|
|
|
|
/* If there are any background reminders queued up, handle them */
|
|
if (NumQueued || Daemon) {
|
|
|
|
if (DontFork) {
|
|
HandleQueuedReminders();
|
|
return 0;
|
|
} else {
|
|
pid = fork();
|
|
if (pid == 0) {
|
|
HandleQueuedReminders();
|
|
return 0;
|
|
}
|
|
if (pid == -1) {
|
|
fprintf(ErrFp, "%s", ErrMsg[E_CANTFORK]);
|
|
return 1;
|
|
}
|
|
}
|
|
}
|
|
if (Iterations) {
|
|
PerIterationInit();
|
|
JulianToday++;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void PurgeEchoLine(char const *fmt, ...)
|
|
{
|
|
va_list argptr;
|
|
va_start(argptr, fmt);
|
|
if (PurgeFP != NULL) {
|
|
(void) vfprintf(PurgeFP, fmt, argptr);
|
|
}
|
|
va_end(argptr);
|
|
|
|
}
|
|
|
|
void
|
|
PerIterationInit(void)
|
|
{
|
|
ClearGlobalOmits();
|
|
DestroyOmitContexts();
|
|
DestroyVars(0);
|
|
DefaultColorR = -1;
|
|
DefaultColorG = -1;
|
|
DefaultColorB = -1;
|
|
NumTriggered = 0;
|
|
ClearLastTriggers();
|
|
}
|
|
|
|
/***************************************************************/
|
|
/* */
|
|
/* DoReminders */
|
|
/* */
|
|
/* The normal case - we're not doing a calendar. */
|
|
/* */
|
|
/***************************************************************/
|
|
static void DoReminders(void)
|
|
{
|
|
int r;
|
|
Token tok;
|
|
char const *s;
|
|
Parser p;
|
|
int purge_handled;
|
|
|
|
if (!UseStdin) {
|
|
FileAccessDate = GetAccessDate(InitialFile);
|
|
} else {
|
|
FileAccessDate = JulianToday;
|
|
}
|
|
|
|
if (FileAccessDate < 0) {
|
|
fprintf(ErrFp, "%s: `%s': %s.\n", ErrMsg[E_CANTACCESS], InitialFile, strerror(errno));
|
|
exit(1);
|
|
}
|
|
|
|
r=IncludeFile(InitialFile);
|
|
if (r) {
|
|
fprintf(ErrFp, "%s %s: %s\n", ErrMsg[E_ERR_READING],
|
|
InitialFile, ErrMsg[r]);
|
|
exit(1);
|
|
}
|
|
|
|
while(1) {
|
|
r = ReadLine();
|
|
if (r == E_EOF) return;
|
|
if (r) {
|
|
Eprint("%s: %s", ErrMsg[E_ERR_READING], ErrMsg[r]);
|
|
exit(1);
|
|
}
|
|
s = FindInitialToken(&tok, CurLine);
|
|
|
|
/* Should we ignore it? */
|
|
if (NumIfs &&
|
|
tok.type != T_If &&
|
|
tok.type != T_Else &&
|
|
tok.type != T_EndIf &&
|
|
tok.type != T_IfTrig &&
|
|
ShouldIgnoreLine())
|
|
{
|
|
/*** IGNORE THE LINE ***/
|
|
if (PurgeMode) {
|
|
if (strncmp(CurLine, "#!P", 3)) {
|
|
PurgeEchoLine("%s\n", CurLine);
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
purge_handled = 0;
|
|
/* Create a parser to parse the line */
|
|
CreateParser(s, &p);
|
|
switch(tok.type) {
|
|
|
|
case T_Empty:
|
|
case T_Comment:
|
|
if (!strncmp(CurLine, "#!P", 3)) {
|
|
purge_handled = 1;
|
|
}
|
|
break;
|
|
|
|
case T_Rem: r=DoRem(&p); purge_handled = 1; break;
|
|
case T_ErrMsg: r=DoErrMsg(&p); break;
|
|
case T_If: r=DoIf(&p); break;
|
|
case T_IfTrig: r=DoIfTrig(&p); break;
|
|
case T_Else: r=DoElse(&p); break;
|
|
case T_EndIf: r=DoEndif(&p); break;
|
|
case T_Include:
|
|
case T_IncludeR:
|
|
/* In purge mode, include closes file, so we
|
|
need to echo it here! */
|
|
if (PurgeMode) {
|
|
PurgeEchoLine("%s\n", CurLine);
|
|
}
|
|
r=DoInclude(&p, tok.type);
|
|
purge_handled = 1;
|
|
break;
|
|
case T_IncludeCmd:
|
|
/* In purge mode, include closes file, so we
|
|
need to echo it here! */
|
|
if (PurgeMode) {
|
|
PurgeEchoLine("%s\n", CurLine);
|
|
}
|
|
r=DoIncludeCmd(&p);
|
|
purge_handled = 1;
|
|
break;
|
|
case T_Exit: DoExit(&p); break;
|
|
case T_Flush: r=DoFlush(&p); break;
|
|
case T_Set: r=DoSet(&p); break;
|
|
case T_Fset: r=DoFset(&p); break;
|
|
case T_UnSet: r=DoUnset(&p); break;
|
|
case T_Clr: r=DoClear(&p); break;
|
|
case T_Debug: r=DoDebug(&p); break;
|
|
case T_Dumpvars: r=DoDump(&p); break;
|
|
case T_Banner: r=DoBanner(&p); break;
|
|
case T_Omit: r=DoOmit(&p);
|
|
if (r == E_PARSE_AS_REM) {
|
|
DestroyParser(&p);
|
|
CreateParser(s, &p);
|
|
r=DoRem(&p);
|
|
purge_handled = 1;
|
|
}
|
|
break;
|
|
case T_Pop: r=PopOmitContext(&p); break;
|
|
case T_Preserve: r=DoPreserve(&p); break;
|
|
case T_Push: r=PushOmitContext(&p); break;
|
|
case T_RemType: if (tok.val == RUN_TYPE) {
|
|
r=DoRun(&p);
|
|
} else {
|
|
CreateParser(CurLine, &p);
|
|
r=DoRem(&p);
|
|
purge_handled = 1;
|
|
}
|
|
break;
|
|
|
|
|
|
/* If we don't recognize the command, do a REM by default */
|
|
/* Note: Since the parser hasn't been used yet, we don't */
|
|
/* need to destroy it here. */
|
|
|
|
default: CreateParser(CurLine, &p); purge_handled = 1; r=DoRem(&p); break;
|
|
|
|
}
|
|
if (r && (!Hush || r != E_RUN_DISABLED)) {
|
|
Eprint("%s", ErrMsg[r]);
|
|
}
|
|
if (PurgeMode) {
|
|
if (!purge_handled) {
|
|
PurgeEchoLine("%s\n", CurLine);
|
|
} else {
|
|
if (r) {
|
|
PurgeEchoLine("#!P! Could not parse next line: %s\n", ErrMsg[r]);
|
|
PurgeEchoLine("%s\n", CurLine);
|
|
}
|
|
}
|
|
}
|
|
/* Destroy the parser - free up resources it may be tying up */
|
|
DestroyParser(&p);
|
|
}
|
|
}
|
|
}
|
|
|
|
/***************************************************************/
|
|
/* */
|
|
/* Julian */
|
|
/* */
|
|
/* Given day, month, year, return Julian date in days since */
|
|
/* 1 January 1990. */
|
|
/* */
|
|
/***************************************************************/
|
|
int Julian(int year, int month, int day)
|
|
{
|
|
int y1 = BASE-1, y2 = year-1;
|
|
|
|
int y4 = (y2 / 4) - (y1 / 4); /* Correct for leap years */
|
|
int y100 = (y2 / 100) - (y1 / 100); /* Don't count multiples of 100... */
|
|
int y400 = (y2 / 400) - (y1 / 400); /* ... but do count multiples of 400 */
|
|
|
|
return 365 * (year-BASE) + y4 - y100 + y400 +
|
|
MonthIndex[IsLeapYear(year)][month] + day - 1;
|
|
}
|
|
|
|
/***************************************************************/
|
|
/* */
|
|
/* FromJulian */
|
|
/* */
|
|
/* Convert a Julian date to year, month, day. You may supply */
|
|
/* NULL for y, m or d if you're not interested in that value */
|
|
/* */
|
|
/***************************************************************/
|
|
void FromJulian(int jul, int *y, int *m, int *d)
|
|
{
|
|
int try_yr = (jul / 365) + BASE;
|
|
int try_mon = 0;
|
|
int t;
|
|
|
|
/* Inline code for speed... */
|
|
int y1 = BASE-1, y2 = try_yr-1;
|
|
int y4 = (y2 / 4) - (y1 / 4); /* Correct for leap years */
|
|
int y100 = (y2 / 100) - (y1 / 100); /* Don't count multiples of 100... */
|
|
int y400 = (y2 / 400) - (y1 / 400); /* ... but do count multiples of 400 */
|
|
|
|
int try_jul= 365 * (try_yr-BASE) + y4 - y100 + y400;
|
|
|
|
while (try_jul > jul) {
|
|
try_yr--;
|
|
try_jul -= DaysInYear(try_yr);
|
|
}
|
|
jul -= try_jul;
|
|
|
|
t = DaysInMonth(try_mon, try_yr);
|
|
while (jul >= t) {
|
|
jul -= t;
|
|
try_mon++;
|
|
t = DaysInMonth(try_mon, try_yr);
|
|
}
|
|
if (y) {
|
|
*y = try_yr;
|
|
}
|
|
if (m) {
|
|
*m = try_mon;
|
|
}
|
|
if (d) {
|
|
*d = jul + 1;
|
|
}
|
|
return;
|
|
}
|
|
|
|
/***************************************************************/
|
|
/* */
|
|
/* ParseChar */
|
|
/* */
|
|
/* Parse a character from a parse pointer. If peek is non- */
|
|
/* zero, then just peek ahead; don't advance pointer. */
|
|
/* */
|
|
/***************************************************************/
|
|
int ParseChar(ParsePtr p, int *err, int peek)
|
|
{
|
|
Value val;
|
|
int r;
|
|
|
|
*err = 0;
|
|
if (p->tokenPushed && *p->tokenPushed) {
|
|
if (peek) return *p->tokenPushed;
|
|
else {
|
|
r = *p->tokenPushed++;
|
|
if (!r) {
|
|
DBufFree(&p->pushedToken);
|
|
p->tokenPushed = NULL;
|
|
}
|
|
return r;
|
|
}
|
|
}
|
|
|
|
while(1) {
|
|
if (p->isnested) {
|
|
if (*(p->epos)) {
|
|
if (peek) {
|
|
return *(p->epos);
|
|
} else {
|
|
return *(p->epos++);
|
|
}
|
|
}
|
|
free((void *) p->etext); /* End of substituted expression */
|
|
p->etext = NULL;
|
|
p->epos = NULL;
|
|
p->isnested = 0;
|
|
}
|
|
if (!*(p->pos)) {
|
|
return 0;
|
|
}
|
|
if (*p->pos != BEG_OF_EXPR || !p->allownested) {
|
|
if (peek) {
|
|
return *(p->pos);
|
|
} else {
|
|
return *(p->pos++);
|
|
}
|
|
}
|
|
p->expr_happened = 1;
|
|
p->pos++;
|
|
r = EvalExpr(&(p->pos), &val, p);
|
|
if (r) {
|
|
*err = r;
|
|
DestroyParser(p);
|
|
return 0;
|
|
}
|
|
if (*p->pos != END_OF_EXPR) {
|
|
*err = E_MISS_END;
|
|
DestroyParser(p);
|
|
DestroyValue(val);
|
|
return 0;
|
|
}
|
|
p->pos++;
|
|
r = DoCoerce(STR_TYPE, &val);
|
|
if (r) { *err = r; return 0; }
|
|
p->etext = val.v.str;
|
|
val.type = ERR_TYPE; /* So it's not accidentally destroyed! */
|
|
p->isnested = 1;
|
|
p->epos = p->etext;
|
|
}
|
|
}
|
|
|
|
/***************************************************************/
|
|
/* */
|
|
/* ParseNonSpaceChar */
|
|
/* */
|
|
/* Parse the next non-space character. */
|
|
/* */
|
|
/***************************************************************/
|
|
int ParseNonSpaceChar(ParsePtr p, int *err, int peek)
|
|
{
|
|
int ch;
|
|
|
|
ch = ParseChar(p, err, 1);
|
|
if (*err) return 0;
|
|
|
|
while (isempty(ch)) {
|
|
ParseChar(p, err, 0); /* Guaranteed to work */
|
|
ch = ParseChar(p, err, 1);
|
|
if (*err) return 0;
|
|
}
|
|
if (!peek) ch = ParseChar(p, err, 0); /* Guaranteed to work */
|
|
return ch;
|
|
}
|
|
|
|
/***************************************************************/
|
|
/* */
|
|
/* ParseToken */
|
|
/* */
|
|
/* Parse a token delimited by whitespace. */
|
|
/* */
|
|
/***************************************************************/
|
|
int ParseToken(ParsePtr p, DynamicBuffer *dbuf)
|
|
{
|
|
int c, err;
|
|
|
|
DBufFree(dbuf);
|
|
|
|
c = ParseChar(p, &err, 0);
|
|
if (err) return err;
|
|
while (c && isempty(c)) {
|
|
c = ParseChar(p, &err, 0);
|
|
if (err) return err;
|
|
}
|
|
if (!c) return OK;
|
|
while (c && !isempty(c)) {
|
|
if (DBufPutc(dbuf, c) != OK) {
|
|
DBufFree(dbuf);
|
|
return E_NO_MEM;
|
|
}
|
|
c = ParseChar(p, &err, 0);
|
|
if (err) {
|
|
DBufFree(dbuf);
|
|
return err;
|
|
}
|
|
}
|
|
return OK;
|
|
}
|
|
|
|
/***************************************************************/
|
|
/* */
|
|
/* ParseIdentifier */
|
|
/* */
|
|
/* Parse a valid identifier - ie, alpha or underscore */
|
|
/* followed by alphanum. Return E_BAD_ID if identifier is */
|
|
/* invalid. */
|
|
/* */
|
|
/***************************************************************/
|
|
int ParseIdentifier(ParsePtr p, DynamicBuffer *dbuf)
|
|
{
|
|
int c, err;
|
|
|
|
DBufFree(dbuf);
|
|
|
|
c = ParseChar(p, &err, 0);
|
|
if (err) return err;
|
|
while (c && isempty(c)) {
|
|
c = ParseChar(p, &err, 0);
|
|
if (err) return err;
|
|
}
|
|
if (!c) return E_EOLN;
|
|
if (c != '$' && c != '_' && !isalpha(c)) return E_BAD_ID;
|
|
if (DBufPutc(dbuf, c) != OK) {
|
|
DBufFree(dbuf);
|
|
return E_NO_MEM;
|
|
}
|
|
|
|
while (1) {
|
|
c = ParseChar(p, &err, 1);
|
|
if (err) {
|
|
DBufFree(dbuf);
|
|
return err;
|
|
}
|
|
if (c != '_' && !isalnum(c)) return OK;
|
|
c = ParseChar(p, &err, 0); /* Guaranteed to work */
|
|
if (DBufPutc(dbuf, c) != OK) {
|
|
DBufFree(dbuf);
|
|
return E_NO_MEM;
|
|
}
|
|
}
|
|
}
|
|
|
|
/***************************************************************/
|
|
/* */
|
|
/* EvaluateExpr */
|
|
/* */
|
|
/* We are expecting an expression here. Evaluate it and */
|
|
/* return the value. */
|
|
/* */
|
|
/***************************************************************/
|
|
int EvaluateExpr(ParsePtr p, Value *v)
|
|
{
|
|
|
|
int bracketed = 0;
|
|
int r;
|
|
|
|
if (p->isnested) return E_PARSE_ERR; /* Can't nest expressions */
|
|
while (isempty(*p->pos)) (p->pos)++;
|
|
if (!p->pos) return E_PARSE_ERR; /* Missing expression */
|
|
if (*p->pos == BEG_OF_EXPR) {
|
|
(p->pos)++;
|
|
bracketed = 1;
|
|
}
|
|
r = EvalExpr(&(p->pos), v, p);
|
|
if (r) return r;
|
|
if (bracketed) {
|
|
if (*p->pos != END_OF_EXPR) return E_MISS_END;
|
|
(p->pos)++;
|
|
}
|
|
return OK;
|
|
}
|
|
|
|
/***************************************************************/
|
|
/* */
|
|
/* Wprint - print a warning message. */
|
|
/* */
|
|
/***************************************************************/
|
|
void Wprint(char const *fmt, ...)
|
|
{
|
|
va_list argptr;
|
|
|
|
|
|
if (FileName) {
|
|
if (strcmp(FileName, "-"))
|
|
(void) fprintf(ErrFp, "%s(%d): ", FileName, LineNo);
|
|
else
|
|
(void) fprintf(ErrFp, "-stdin-(%d): ", LineNo);
|
|
}
|
|
|
|
va_start(argptr, fmt);
|
|
(void) vfprintf(ErrFp, fmt, argptr);
|
|
(void) fputc('\n', ErrFp);
|
|
va_end(argptr);
|
|
return;
|
|
}
|
|
/***************************************************************/
|
|
/* */
|
|
/* Eprint - print an error message. */
|
|
/* */
|
|
/***************************************************************/
|
|
void Eprint(char const *fmt, ...)
|
|
{
|
|
va_list argptr;
|
|
|
|
/* Check if more than one error msg. from this line */
|
|
if (!FreshLine && !ShowAllErrors) return;
|
|
|
|
if (FreshLine && FileName) {
|
|
FreshLine = 0;
|
|
if (strcmp(FileName, "-")) {
|
|
(void) fprintf(ErrFp, "%s(%d): ", FileName, LineNo);
|
|
if (print_callstack(ErrFp)) {
|
|
(void) fprintf(ErrFp, ": ");
|
|
}
|
|
} else {
|
|
(void) fprintf(ErrFp, "-stdin-(%d): ", LineNo);
|
|
if (print_callstack(ErrFp)) {
|
|
(void) fprintf(ErrFp, ": ");
|
|
}
|
|
}
|
|
if (DebugFlag & DB_PRTLINE) OutputLine(ErrFp);
|
|
} else if (FileName) {
|
|
fprintf(ErrFp, " ");
|
|
if (print_callstack(ErrFp)) {
|
|
(void) fprintf(ErrFp, ": ");
|
|
}
|
|
}
|
|
|
|
va_start(argptr, fmt);
|
|
(void) vfprintf(ErrFp, fmt, argptr);
|
|
(void) fputc('\n', ErrFp);
|
|
va_end(argptr);
|
|
return;
|
|
}
|
|
|
|
/***************************************************************/
|
|
/* */
|
|
/* OutputLine */
|
|
/* */
|
|
/* Output a line from memory buffer to a file pointer. This */
|
|
/* simply involves escaping newlines. */
|
|
/* */
|
|
/***************************************************************/
|
|
void OutputLine(FILE *fp)
|
|
{
|
|
char const *s = CurLine;
|
|
char c = 0;
|
|
|
|
while (*s) {
|
|
if (*s == '\n') putc('\\', fp);
|
|
putc(*s, fp);
|
|
c = *s++;
|
|
}
|
|
if (c != '\n') putc('\n', fp);
|
|
}
|
|
|
|
/***************************************************************/
|
|
/* */
|
|
/* CreateParser */
|
|
/* */
|
|
/* Create a parser given a string buffer */
|
|
/* */
|
|
/***************************************************************/
|
|
void CreateParser(char const *s, ParsePtr p)
|
|
{
|
|
p->text = s;
|
|
p->pos = s;
|
|
p->isnested = 0;
|
|
p->epos = NULL;
|
|
p->etext = NULL;
|
|
p->allownested = 1;
|
|
p->tokenPushed = NULL;
|
|
p->expr_happened = 0;
|
|
p->nonconst_expr = 0;
|
|
DBufInit(&p->pushedToken);
|
|
}
|
|
|
|
/***************************************************************/
|
|
/* */
|
|
/* DestroyParser */
|
|
/* */
|
|
/* Destroy a parser, freeing up resources used. */
|
|
/* */
|
|
/***************************************************************/
|
|
void DestroyParser(ParsePtr p)
|
|
{
|
|
if (p->isnested && p->etext) {
|
|
free((void *) p->etext);
|
|
p->etext = NULL;
|
|
p->isnested = 0;
|
|
}
|
|
DBufFree(&p->pushedToken);
|
|
}
|
|
|
|
/***************************************************************/
|
|
/* */
|
|
/* PushToken - one level of token pushback. This is */
|
|
/* on a per-parser basis. */
|
|
/* */
|
|
/***************************************************************/
|
|
int PushToken(char const *tok, ParsePtr p)
|
|
{
|
|
DBufFree(&p->pushedToken);
|
|
if (DBufPuts(&p->pushedToken, tok) != OK ||
|
|
DBufPutc(&p->pushedToken, ' ') != OK) {
|
|
DBufFree(&p->pushedToken);
|
|
return E_NO_MEM;
|
|
}
|
|
p->tokenPushed = DBufValue(&p->pushedToken);
|
|
return OK;
|
|
}
|
|
|
|
/***************************************************************/
|
|
/* */
|
|
/* SystemTime */
|
|
/* */
|
|
/* Return the system time in seconds past midnight */
|
|
/* */
|
|
/***************************************************************/
|
|
long SystemTime(int realtime)
|
|
{
|
|
time_t tloc;
|
|
struct tm *t;
|
|
|
|
if (!realtime && (SysTime != -1L)) return SysTime;
|
|
|
|
(void) time(&tloc);
|
|
t = localtime(&tloc);
|
|
return (long) t->tm_hour * 3600L + (long) t->tm_min * 60L +
|
|
(long) t->tm_sec;
|
|
}
|
|
|
|
/***************************************************************/
|
|
/* */
|
|
/* SystemDate */
|
|
/* */
|
|
/* Obtains today's date. Returns Julian date or -1 for */
|
|
/* failure. (Failure happens if sys date is before BASE */
|
|
/* year.) */
|
|
/* */
|
|
/***************************************************************/
|
|
int SystemDate(int *y, int *m, int *d)
|
|
{
|
|
time_t tloc;
|
|
struct tm *t;
|
|
|
|
(void) time(&tloc);
|
|
t = localtime(&tloc);
|
|
|
|
*d = t->tm_mday;
|
|
*m = t->tm_mon;
|
|
*y = t->tm_year + 1900;
|
|
|
|
return Julian(*y, *m, *d);
|
|
}
|
|
|
|
|
|
/***************************************************************/
|
|
/* */
|
|
/* DoIf - handle the IF command. */
|
|
/* */
|
|
/***************************************************************/
|
|
int DoIf(ParsePtr p)
|
|
{
|
|
Value v;
|
|
int r;
|
|
unsigned syndrome;
|
|
|
|
if ((size_t) NumIfs >= IF_NEST) return E_NESTED_IF;
|
|
|
|
if (ShouldIgnoreLine()) syndrome = IF_TRUE | BEFORE_ELSE;
|
|
else {
|
|
if ( (r = EvaluateExpr(p, &v)) ) {
|
|
syndrome = IF_TRUE | BEFORE_ELSE;
|
|
Eprint("%s", ErrMsg[r]);
|
|
} else
|
|
if ( (v.type != STR_TYPE && v.v.val) ||
|
|
(v.type == STR_TYPE && strcmp(v.v.str, "")) ) {
|
|
syndrome = IF_TRUE | BEFORE_ELSE;
|
|
} else {
|
|
syndrome = IF_FALSE | BEFORE_ELSE;
|
|
if (PurgeMode) {
|
|
PurgeEchoLine("%s\n", "#!P: The next IF evaluated false...");
|
|
PurgeEchoLine("%s\n", "#!P: REM statements in IF block not checked for purging.");
|
|
}
|
|
}
|
|
}
|
|
|
|
NumIfs++;
|
|
IfFlags &= ~(IF_MASK << (2*NumIfs - 2));
|
|
IfFlags |= syndrome << (2 * NumIfs - 2);
|
|
if (ShouldIgnoreLine()) return OK;
|
|
return VerifyEoln(p);
|
|
}
|
|
|
|
|
|
/***************************************************************/
|
|
/* */
|
|
/* DoElse - handle the ELSE command. */
|
|
/* */
|
|
/***************************************************************/
|
|
int DoElse(ParsePtr p)
|
|
{
|
|
unsigned syndrome;
|
|
|
|
int was_ignoring = ShouldIgnoreLine();
|
|
|
|
if (!NumIfs) return E_ELSE_NO_IF;
|
|
|
|
syndrome = IfFlags >> (2 * NumIfs - 2);
|
|
|
|
if ((syndrome & IF_ELSE_MASK) == AFTER_ELSE) return E_ELSE_NO_IF;
|
|
|
|
IfFlags |= AFTER_ELSE << (2 * NumIfs - 2);
|
|
if (PurgeMode && ShouldIgnoreLine() && !was_ignoring) {
|
|
PurgeEchoLine("%s\n", "#!P: The previous IF evaluated true.");
|
|
PurgeEchoLine("%s\n", "#!P: REM statements in ELSE block not checked for purging");
|
|
}
|
|
return VerifyEoln(p);
|
|
}
|
|
|
|
/***************************************************************/
|
|
/* */
|
|
/* DoEndif - handle the Endif command. */
|
|
/* */
|
|
/***************************************************************/
|
|
int DoEndif(ParsePtr p)
|
|
{
|
|
if (!NumIfs) return E_ENDIF_NO_IF;
|
|
NumIfs--;
|
|
return VerifyEoln(p);
|
|
}
|
|
|
|
/***************************************************************/
|
|
/* */
|
|
/* DoIfTrig */
|
|
/* */
|
|
/* Handle the IFTRIG command. */
|
|
/* */
|
|
/***************************************************************/
|
|
int DoIfTrig(ParsePtr p)
|
|
{
|
|
int r, err;
|
|
unsigned syndrome;
|
|
Trigger trig;
|
|
TimeTrig tim;
|
|
int jul;
|
|
|
|
|
|
if ((size_t) NumIfs >= IF_NEST) return E_NESTED_IF;
|
|
if (ShouldIgnoreLine()) syndrome = IF_TRUE | BEFORE_ELSE;
|
|
else {
|
|
if ( (r=ParseRem(p, &trig, &tim, 1)) ) return r;
|
|
if (trig.typ != NO_TYPE) return E_PARSE_ERR;
|
|
jul = ComputeTrigger(trig.scanfrom, &trig, &tim, &r, 1);
|
|
if (r) {
|
|
if (r != E_CANT_TRIG || !trig.maybe_uncomputable) {
|
|
if (!Hush || r != E_RUN_DISABLED) {
|
|
Eprint("%s", ErrMsg[r]);
|
|
}
|
|
}
|
|
syndrome = IF_FALSE | BEFORE_ELSE;
|
|
}
|
|
else {
|
|
if (ShouldTriggerReminder(&trig, &tim, jul, &err)) {
|
|
syndrome = IF_TRUE | BEFORE_ELSE;
|
|
} else {
|
|
syndrome = IF_FALSE | BEFORE_ELSE;
|
|
}
|
|
}
|
|
if (syndrome == (IF_FALSE | BEFORE_ELSE) && PurgeMode) {
|
|
PurgeEchoLine("%s\n", "#!P: The next IFTRIG did not trigger.");
|
|
PurgeEchoLine("%s\n", "#!P: REM statements in IFTRIG block not checked for purging.");
|
|
}
|
|
FreeTrig(&trig);
|
|
}
|
|
NumIfs++;
|
|
IfFlags &= ~(IF_MASK << (2*NumIfs - 2));
|
|
IfFlags |= syndrome << (2 * NumIfs - 2);
|
|
return OK;
|
|
}
|
|
|
|
|
|
/***************************************************************/
|
|
/* */
|
|
/* ShouldIgnoreLine - given the current state of the IF */
|
|
/* stack, should we ignore the current line? */
|
|
/* */
|
|
/***************************************************************/
|
|
int ShouldIgnoreLine(void)
|
|
{
|
|
register int i, syndrome;
|
|
|
|
/* Algorithm - go from outer to inner, and if any should be ignored, then
|
|
ignore the whole. */
|
|
|
|
for (i=0; i<NumIfs; i++) {
|
|
syndrome = (IfFlags >> (i*2)) & IF_MASK;
|
|
if (syndrome == IF_TRUE+AFTER_ELSE ||
|
|
syndrome == IF_FALSE+BEFORE_ELSE) return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/***************************************************************/
|
|
/* */
|
|
/* VerifyEoln */
|
|
/* */
|
|
/* Verify that current line contains no more tokens. */
|
|
/* */
|
|
/***************************************************************/
|
|
int VerifyEoln(ParsePtr p)
|
|
{
|
|
int r;
|
|
|
|
DynamicBuffer buf;
|
|
DBufInit(&buf);
|
|
|
|
if ( (r = ParseToken(p, &buf)) ) return r;
|
|
if (*DBufValue(&buf) &&
|
|
(*DBufValue(&buf) != '#') &&
|
|
(*DBufValue(&buf) != ';')) {
|
|
Eprint("%s: `%s'", ErrMsg[E_EXPECTING_EOL], DBufValue(&buf));
|
|
DBufFree(&buf);
|
|
return E_EXTRANEOUS_TOKEN;
|
|
}
|
|
DBufFree(&buf);
|
|
return OK;
|
|
}
|
|
|
|
/***************************************************************/
|
|
/* */
|
|
/* DoDebug */
|
|
/* */
|
|
/* Set the debug options under program control. */
|
|
/* */
|
|
/***************************************************************/
|
|
int DoDebug(ParsePtr p)
|
|
{
|
|
int err;
|
|
int ch;
|
|
int val=1;
|
|
|
|
while(1) {
|
|
ch = ParseChar(p, &err, 0);
|
|
if (err) return err;
|
|
switch(ch) {
|
|
case '#':
|
|
case ';':
|
|
case 0:
|
|
return OK;
|
|
|
|
case ' ':
|
|
case '\t':
|
|
break;
|
|
|
|
case '+':
|
|
val = 1;
|
|
break;
|
|
|
|
case '-':
|
|
val = 0;
|
|
break;
|
|
|
|
case 'e':
|
|
case 'E':
|
|
if (val) DebugFlag |= DB_ECHO_LINE;
|
|
else DebugFlag &= ~DB_ECHO_LINE;
|
|
break;
|
|
|
|
case 'x':
|
|
case 'X':
|
|
if (val) DebugFlag |= DB_PRTEXPR;
|
|
else DebugFlag &= ~DB_PRTEXPR;
|
|
break;
|
|
|
|
case 't':
|
|
case 'T':
|
|
if (val) DebugFlag |= DB_PRTTRIG;
|
|
else DebugFlag &= ~DB_PRTTRIG;
|
|
break;
|
|
|
|
case 'v':
|
|
case 'V':
|
|
if (val) DebugFlag |= DB_DUMP_VARS;
|
|
else DebugFlag &= ~DB_DUMP_VARS;
|
|
break;
|
|
|
|
case 'l':
|
|
case 'L':
|
|
if (val) DebugFlag |= DB_PRTLINE;
|
|
else DebugFlag &= ~DB_PRTLINE;
|
|
break;
|
|
|
|
case 'f':
|
|
case 'F':
|
|
if (val) DebugFlag |= DB_TRACE_FILES;
|
|
else DebugFlag &= ~DB_TRACE_FILES;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/***************************************************************/
|
|
/* */
|
|
/* DoBanner */
|
|
/* */
|
|
/* Set the banner to be printed just before the first */
|
|
/* reminder is issued. */
|
|
/* */
|
|
/***************************************************************/
|
|
int DoBanner(ParsePtr p)
|
|
{
|
|
int err;
|
|
int c;
|
|
DynamicBuffer buf;
|
|
|
|
DBufInit(&buf);
|
|
c = ParseChar(p, &err, 0);
|
|
if (err) return err;
|
|
while (isempty(c)) {
|
|
c = ParseChar(p, &err, 0);
|
|
if (err) return err;
|
|
}
|
|
if (!c) return E_EOLN;
|
|
|
|
while(c) {
|
|
if (DBufPutc(&buf, c) != OK) return E_NO_MEM;
|
|
c = ParseChar(p, &err, 0);
|
|
if (err) {
|
|
DBufFree(&buf);
|
|
return err;
|
|
}
|
|
}
|
|
DBufFree(&Banner);
|
|
|
|
err = DBufPuts(&Banner, DBufValue(&buf));
|
|
DBufFree(&buf);
|
|
return err;
|
|
}
|
|
|
|
/***************************************************************/
|
|
/* */
|
|
/* DoRun */
|
|
/* */
|
|
/* Enable or disable the RUN command under program control */
|
|
/* */
|
|
/* */
|
|
/***************************************************************/
|
|
int DoRun(ParsePtr p)
|
|
{
|
|
int r;
|
|
|
|
DynamicBuffer buf;
|
|
DBufInit(&buf);
|
|
|
|
if ( (r=ParseToken(p, &buf)) ) return r;
|
|
|
|
/* Only allow RUN ON in top-level script */
|
|
if (! StrCmpi(DBufValue(&buf), "ON")) {
|
|
if (TopLevel()) RunDisabled &= ~RUN_SCRIPT;
|
|
}
|
|
/* But allow RUN OFF anywhere */
|
|
else if (! StrCmpi(DBufValue(&buf), "OFF"))
|
|
RunDisabled |= RUN_SCRIPT;
|
|
else {
|
|
DBufFree(&buf);
|
|
return E_PARSE_ERR;
|
|
}
|
|
DBufFree(&buf);
|
|
|
|
return VerifyEoln(p);
|
|
}
|
|
|
|
/***************************************************************/
|
|
/* */
|
|
/* DoFlush */
|
|
/* */
|
|
/* Flush stdout and stderr */
|
|
/* */
|
|
/***************************************************************/
|
|
int DoFlush(ParsePtr p)
|
|
{
|
|
fflush(stdout);
|
|
fflush(stderr);
|
|
return VerifyEoln(p);
|
|
}
|
|
|
|
/***************************************************************/
|
|
/* */
|
|
/* DoExit */
|
|
/* */
|
|
/* Handle the EXIT command. */
|
|
/* */
|
|
/***************************************************************/
|
|
void DoExit(ParsePtr p)
|
|
{
|
|
int r;
|
|
Value v;
|
|
|
|
if (PurgeMode) return;
|
|
|
|
r = EvaluateExpr(p, &v);
|
|
if (r || v.type != INT_TYPE) exit(99);
|
|
exit(v.v.val);
|
|
}
|
|
|
|
/***************************************************************/
|
|
/* */
|
|
/* DoErrMsg */
|
|
/* */
|
|
/* Issue an error message under program control. */
|
|
/* */
|
|
/***************************************************************/
|
|
int DoErrMsg(ParsePtr p)
|
|
{
|
|
TimeTrig tt;
|
|
Trigger t;
|
|
int r;
|
|
char const *s;
|
|
|
|
DynamicBuffer buf;
|
|
|
|
if (PurgeMode) return OK;
|
|
|
|
DBufInit(&buf);
|
|
t.typ = MSG_TYPE;
|
|
tt.ttime = SystemTime(0) / 60;
|
|
if ( (r=DoSubst(p, &buf, &t, &tt, JulianToday, NORMAL_MODE)) ) {
|
|
return r;
|
|
}
|
|
s = DBufValue(&buf);
|
|
while (isempty(*s)) s++;
|
|
fprintf(ErrFp, "%s\n", s);
|
|
DBufFree(&buf);
|
|
return OK;
|
|
}
|
|
|
|
/***************************************************************/
|
|
/* */
|
|
/* CalcMinsFromUTC */
|
|
/* */
|
|
/* Attempt to calculate the minutes from UTC for a specific */
|
|
/* date. */
|
|
/* */
|
|
/***************************************************************/
|
|
|
|
/* The array FoldArray[2][7] contains sample years which begin
|
|
on the specified weekday. For example, FoldArray[0][2] is a
|
|
non-leap year beginning on Wednesday, and FoldArray[1][5] is a
|
|
leap year beginning on Saturday. Used to fold back dates which
|
|
are too high for the standard Unix representation.
|
|
NOTE: This implies that you cannot set BASE > 2001!!!!! */
|
|
static int FoldArray[2][7] = {
|
|
{2001, 2002, 2003, 2009, 2010, 2005, 2006},
|
|
{2024, 2008, 2020, 2004, 2016, 2000, 2012}
|
|
};
|
|
|
|
int CalcMinsFromUTC(int jul, int tim, int *mins, int *isdst)
|
|
{
|
|
|
|
/* Convert jul and tim to an Unix tm struct */
|
|
int yr, mon, day;
|
|
int tdiff;
|
|
struct tm local, utc, *temp;
|
|
time_t loc_t, utc_t;
|
|
int isdst_tmp;
|
|
|
|
FromJulian(jul, &yr, &mon, &day);
|
|
|
|
/* If the year is greater than 2037, some Unix machines have problems.
|
|
Fold it back to a "similar" year and trust that the UTC calculations
|
|
are still valid... */
|
|
if (FoldYear && yr>2037) {
|
|
jul = Julian(yr, 0, 1);
|
|
yr = FoldArray[IsLeapYear(yr)][jul%7];
|
|
}
|
|
local.tm_sec = 0;
|
|
local.tm_min = tim % 60;
|
|
local.tm_hour = tim / 60;
|
|
|
|
|
|
local.tm_mday = day;
|
|
local.tm_mon = mon;
|
|
local.tm_year = yr-1900;
|
|
local.tm_isdst = -1; /* We don't know whether or not dst is in effect */
|
|
|
|
|
|
/* Horrible contortions to get minutes from UTC portably */
|
|
loc_t = mktime(&local);
|
|
if (loc_t == -1) return 1;
|
|
isdst_tmp = local.tm_isdst;
|
|
local.tm_isdst = 0;
|
|
loc_t = mktime(&local);
|
|
if (loc_t == -1) return 1;
|
|
temp = gmtime(&loc_t);
|
|
utc = *temp;
|
|
utc.tm_isdst = 0;
|
|
utc_t = mktime(&utc);
|
|
if (utc_t == -1) return 1;
|
|
/* Compute difference between local time and UTC in seconds.
|
|
Be careful, since time_t might be unsigned. */
|
|
|
|
tdiff = (int) difftime(loc_t, utc_t);
|
|
if (isdst_tmp) tdiff += 60*60;
|
|
if (mins) *mins = (int)(tdiff / 60);
|
|
if (isdst) *isdst = isdst_tmp;
|
|
return 0;
|
|
}
|
|
|
|
static char const *OutputEscapeSequences(char const *s, int print)
|
|
{
|
|
while (*s == 0x1B && *(s+1) == '[') {
|
|
if (print) putchar(*s);
|
|
s++;
|
|
if (print) putchar(*s);
|
|
s++;
|
|
while (*s && (*s < 0x40 || *s > 0x7E)) {
|
|
if (print) putchar(*s);
|
|
s++;
|
|
}
|
|
if (*s) {
|
|
if (print) putchar(*s);
|
|
s++;
|
|
}
|
|
}
|
|
return s;
|
|
}
|
|
|
|
#ifdef REM_USE_WCHAR
|
|
#define ISWBLANK(c) (iswspace(c) && (c) != '\n')
|
|
static wchar_t const *OutputEscapeSequencesWS(wchar_t const *s, int print)
|
|
{
|
|
while (*s == 0x1B && *(s+1) == '[') {
|
|
if (print) PutWideChar(*s);
|
|
s++;
|
|
if (print) PutWideChar(*s);
|
|
s++;
|
|
while (*s && (*s < 0x40 || *s > 0x7E)) {
|
|
if (print) PutWideChar(*s);
|
|
s++;
|
|
}
|
|
if (*s) {
|
|
if (print) PutWideChar(*s);
|
|
s++;
|
|
}
|
|
}
|
|
return s;
|
|
}
|
|
|
|
|
|
static void
|
|
FillParagraphWCAux(wchar_t const *s)
|
|
{
|
|
int line = 0;
|
|
int i, j;
|
|
int doublespace = 1;
|
|
int pendspace;
|
|
int len;
|
|
wchar_t const *t;
|
|
|
|
int roomleft;
|
|
/* Start formatting */
|
|
while(1) {
|
|
|
|
/* If it's a carriage return, output it and start new paragraph */
|
|
if (*s == '\n') {
|
|
putchar('\n');
|
|
s++;
|
|
line = 0;
|
|
while(ISWBLANK(*s)) s++;
|
|
continue;
|
|
}
|
|
if (!*s) {
|
|
return;
|
|
}
|
|
/* Over here, we're at the beginning of a line. Emit the correct
|
|
number of spaces */
|
|
j = line ? SubsIndent : FirstIndent;
|
|
for (i=0; i<j; i++) {
|
|
putchar(' ');
|
|
}
|
|
|
|
/* Calculate the amount of room left on this line */
|
|
roomleft = FormWidth - j;
|
|
pendspace = 0;
|
|
|
|
/* Emit words until the next one won't fit */
|
|
while(1) {
|
|
while(ISWBLANK(*s)) s++;
|
|
if (*s == '\n') break;
|
|
while(1) {
|
|
t = s;
|
|
s = OutputEscapeSequencesWS(s, 1);
|
|
if (s == t) break;
|
|
while(ISWBLANK(*s)) s++;
|
|
}
|
|
t = s;
|
|
len = 0;
|
|
while(*s && !iswspace(*s)) {
|
|
if (*s == 0x1B && *(s+1) == '[') {
|
|
s = OutputEscapeSequencesWS(s, 0);
|
|
continue;
|
|
}
|
|
len += wcwidth(*s);
|
|
s++;
|
|
}
|
|
if (s == t) {
|
|
return;
|
|
}
|
|
if (!pendspace || len+pendspace <= roomleft) {
|
|
for (i=0; i<pendspace; i++) {
|
|
putchar(' ');
|
|
}
|
|
while(t < s) {
|
|
PutWideChar(*t);
|
|
if (strchr(EndSent, *t)) doublespace = 2;
|
|
else if (!strchr(EndSentIg, *t)) doublespace = 1;
|
|
t++;
|
|
}
|
|
} else {
|
|
s = t;
|
|
putchar('\n');
|
|
line++;
|
|
break;
|
|
}
|
|
roomleft -= len+doublespace;
|
|
pendspace = doublespace;
|
|
}
|
|
}
|
|
}
|
|
|
|
static int
|
|
FillParagraphWC(char const *s)
|
|
{
|
|
size_t len;
|
|
wchar_t *buf;
|
|
|
|
len = mbstowcs(NULL, s, 0);
|
|
if (len == (size_t) -1) return E_NO_MEM;
|
|
buf = calloc(len+1, sizeof(wchar_t));
|
|
if (!buf) return E_NO_MEM;
|
|
(void) mbstowcs(buf, s, len+1);
|
|
FillParagraphWCAux(buf);
|
|
free(buf);
|
|
return OK;
|
|
}
|
|
#endif
|
|
/***************************************************************/
|
|
/* */
|
|
/* FillParagraph */
|
|
/* */
|
|
/* Write a string to standard output, formatting it as a */
|
|
/* paragraph according to the FirstIndent, FormWidth and */
|
|
/* SubsIndent variables. Spaces are gobbled. Double-spaces */
|
|
/* are inserted after '.', '?' and '!'. Newlines in the */
|
|
/* source are treated as paragraph breaks. */
|
|
/* */
|
|
/***************************************************************/
|
|
|
|
/* A macro safe ONLY if used with arg with no side effects! */
|
|
#define ISBLANK(c) (isspace(c) && (c) != '\n')
|
|
|
|
void FillParagraph(char const *s)
|
|
{
|
|
|
|
int line = 0;
|
|
int i, j;
|
|
int doublespace = 1;
|
|
int pendspace;
|
|
int len;
|
|
char const *t;
|
|
|
|
int roomleft;
|
|
if (!s || !*s) return;
|
|
|
|
/* Skip leading spaces */
|
|
while(ISBLANK(*s)) s++;
|
|
if (!*s) return;
|
|
|
|
#ifdef REM_USE_WCHAR
|
|
if (FillParagraphWC(s) == OK) {
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
/* Start formatting */
|
|
while(1) {
|
|
|
|
/* If it's a carriage return, output it and start new paragraph */
|
|
if (*s == '\n') {
|
|
putchar('\n');
|
|
s++;
|
|
line = 0;
|
|
while(ISBLANK(*s)) s++;
|
|
continue;
|
|
}
|
|
if (!*s) {
|
|
return;
|
|
}
|
|
/* Over here, we're at the beginning of a line. Emit the correct
|
|
number of spaces */
|
|
j = line ? SubsIndent : FirstIndent;
|
|
for (i=0; i<j; i++) {
|
|
putchar(' ');
|
|
}
|
|
|
|
/* Calculate the amount of room left on this line */
|
|
roomleft = FormWidth - j;
|
|
pendspace = 0;
|
|
|
|
/* Emit words until the next one won't fit */
|
|
while(1) {
|
|
while(ISBLANK(*s)) s++;
|
|
if (*s == '\n') break;
|
|
while(1) {
|
|
t = s;
|
|
s = OutputEscapeSequences(s, 1);
|
|
if (s == t) break;
|
|
while(ISBLANK(*s)) s++;
|
|
}
|
|
t = s;
|
|
len = 0;
|
|
while(*s && !isspace(*s)) {
|
|
if (*s == 0x1B && *(s+1) == '[') {
|
|
s = OutputEscapeSequences(s, 0);
|
|
continue;
|
|
}
|
|
s++;
|
|
len++;
|
|
}
|
|
if (s == t) {
|
|
return;
|
|
}
|
|
if (!pendspace || len+pendspace <= roomleft) {
|
|
for (i=0; i<pendspace; i++) {
|
|
putchar(' ');
|
|
}
|
|
while(t < s) {
|
|
putchar(*t);
|
|
if (strchr(EndSent, *t)) doublespace = 2;
|
|
else if (!strchr(EndSentIg, *t)) doublespace = 1;
|
|
t++;
|
|
}
|
|
} else {
|
|
s = t;
|
|
putchar('\n');
|
|
line++;
|
|
break;
|
|
}
|
|
roomleft -= len+doublespace;
|
|
pendspace = doublespace;
|
|
}
|
|
}
|
|
}
|
|
|
|
/***************************************************************/
|
|
/* */
|
|
/* LocalToUTC */
|
|
/* */
|
|
/* Convert a local date/time to a UTC date/time. */
|
|
/* */
|
|
/***************************************************************/
|
|
void LocalToUTC(int locdate, int loctime, int *utcdate, int *utctime)
|
|
{
|
|
int diff;
|
|
int dummy;
|
|
|
|
if (!CalculateUTC || CalcMinsFromUTC(locdate, loctime, &diff, &dummy))
|
|
diff=MinsFromUTC;
|
|
|
|
loctime -= diff;
|
|
if (loctime < 0) {
|
|
loctime += MINUTES_PER_DAY;
|
|
locdate--;
|
|
} else if (loctime >= MINUTES_PER_DAY) {
|
|
loctime -= MINUTES_PER_DAY;
|
|
locdate++;
|
|
}
|
|
*utcdate = locdate;
|
|
*utctime = loctime;
|
|
}
|
|
|
|
/***************************************************************/
|
|
/* */
|
|
/* UTCToLocal */
|
|
/* */
|
|
/* Convert a UTC date/time to a local date/time. */
|
|
/* */
|
|
/***************************************************************/
|
|
void UTCToLocal(int utcdate, int utctime, int *locdate, int *loctime)
|
|
{
|
|
int diff;
|
|
int dummy;
|
|
|
|
/* Hack -- not quite right when DST changes. */
|
|
if (!CalculateUTC || CalcMinsFromUTC(utcdate, utctime, &diff, &dummy))
|
|
diff=MinsFromUTC;
|
|
|
|
utctime += diff;
|
|
if (utctime < 0) {
|
|
utctime += MINUTES_PER_DAY;
|
|
utcdate--;
|
|
} else if (utctime >= MINUTES_PER_DAY) {
|
|
utctime -= MINUTES_PER_DAY;
|
|
utcdate++;
|
|
}
|
|
*locdate = utcdate;
|
|
*loctime = utctime;
|
|
}
|
|
|
|
/***************************************************************/
|
|
/* */
|
|
/* SigIntHandler */
|
|
/* */
|
|
/* For debugging purposes, when sent a SIGINT, we print the */
|
|
/* contents of the queue. This does NOT work when the -f */
|
|
/* command-line flag is supplied. */
|
|
/* */
|
|
/***************************************************************/
|
|
static sig_atomic_t got_sigint = 0;
|
|
|
|
void
|
|
SigIntHandler(int d)
|
|
{
|
|
UNUSED(d);
|
|
got_sigint = 1;
|
|
}
|
|
|
|
int
|
|
GotSigInt(void)
|
|
{
|
|
if (got_sigint) {
|
|
got_sigint = 0;
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
AppendTag(DynamicBuffer *buf, char const *s)
|
|
{
|
|
if (*(DBufValue(buf))) {
|
|
DBufPutc(buf, ',');
|
|
}
|
|
DBufPuts(buf, s);
|
|
}
|
|
|
|
void
|
|
FreeTrig(Trigger *t)
|
|
{
|
|
DBufFree(&(t->tags));
|
|
}
|
|
|
|
void
|
|
ClearLastTriggers(void)
|
|
{
|
|
LastTrigger.expired = 0;
|
|
LastTrigger.wd = NO_WD;
|
|
LastTrigger.d = NO_DAY;
|
|
LastTrigger.m = NO_MON;
|
|
LastTrigger.y = NO_YR;
|
|
LastTrigger.back = NO_BACK;
|
|
LastTrigger.delta = NO_DELTA;
|
|
LastTrigger.rep = NO_REP;
|
|
LastTrigger.localomit = NO_WD;
|
|
LastTrigger.skip = NO_SKIP;
|
|
LastTrigger.until = NO_UNTIL;
|
|
LastTrigger.typ = NO_TYPE;
|
|
LastTrigger.once = NO_ONCE;
|
|
LastTrigger.scanfrom = NO_DATE;
|
|
LastTrigger.from = NO_DATE;
|
|
LastTrigger.priority = DefaultPrio;
|
|
LastTrigger.sched[0] = 0;
|
|
LastTrigger.warn[0] = 0;
|
|
LastTrigger.omitfunc[0] = 0;
|
|
LastTrigger.passthru[0] = 0;
|
|
DBufFree(&(LastTrigger.tags));
|
|
|
|
LastTimeTrig.ttime = NO_TIME;
|
|
LastTimeTrig.delta = NO_DELTA;
|
|
LastTimeTrig.rep = NO_REP;
|
|
LastTimeTrig.duration = NO_TIME;
|
|
}
|
|
|
|
void
|
|
SaveAllTriggerInfo(Trigger const *t, TimeTrig const *tt, int trigdate, int trigtime, int valid)
|
|
{
|
|
SaveLastTrigger(t);
|
|
SaveLastTimeTrig(tt);
|
|
LastTriggerDate = trigdate;
|
|
LastTriggerTime = trigtime;
|
|
LastTrigValid = valid;
|
|
}
|
|
|
|
void
|
|
SaveLastTrigger(Trigger const *t)
|
|
{
|
|
DBufFree(&(LastTrigger.tags));
|
|
memcpy(&LastTrigger, t, sizeof(LastTrigger));
|
|
DBufInit(&(LastTrigger.tags));
|
|
DBufPuts(&(LastTrigger.tags), DBufValue(&(t->tags)));
|
|
}
|
|
|
|
void
|
|
SaveLastTimeTrig(TimeTrig const *t)
|
|
{
|
|
memcpy(&LastTimeTrig, t, sizeof(LastTimeTrig));
|
|
}
|
|
|
|
/* Wrapper to ignore warnings about ignoring return value of system() */
|
|
void
|
|
System(char const *cmd)
|
|
{
|
|
int r;
|
|
r = system(cmd);
|
|
if (r == 0) {
|
|
r = 1;
|
|
}
|
|
}
|
|
|
|
char const *
|
|
get_day_name(int wkday)
|
|
{
|
|
if (wkday < 0 || wkday > 6) {
|
|
return "INVALID_WKDAY";
|
|
}
|
|
if (DynamicDayName[wkday]) return DynamicDayName[wkday];
|
|
return DayName[wkday];
|
|
}
|
|
|
|
char const *
|
|
get_month_name(int mon)
|
|
{
|
|
if (mon < 0 || mon > 11) {
|
|
return "INVALID_MON";
|
|
}
|
|
if (DynamicMonthName[mon]) return DynamicMonthName[mon];
|
|
return MonthName[mon];
|
|
}
|