/***************************************************************/ /* */ /* MAIN.C */ /* */ /* Main program loop, as well as miscellaneous conversion */ /* routines, etc. */ /* */ /* This file is part of REMIND. */ /* Copyright (C) 1992-1998 by David F. Skoll */ /* Copyright (C) 1999-2000 by Roaring Penguin Software Inc. */ /* */ /***************************************************************/ #include "config.h" #include #include #include #include #include #include #include #ifdef TIME_WITH_SYS_TIME #include #include #else #if defined(HAVE_SYS_TIME_H) || defined (TIME_WITH_SYS_TIME) #include #else #include #endif #endif #include #include "types.h" #include "protos.h" #include "expr.h" #include "globals.h" #include "err.h" static void DoReminders(void); /* Whooo... the putchar/Putchar/PutChar macros are a mess... my apologies... */ #define Putchar(c) PutChar(c) /***************************************************************/ /***************************************************************/ /** **/ /** Main Program Loop **/ /** **/ /***************************************************************/ /***************************************************************/ int main(int argc, char *argv[]) { int pid; /* The very first thing to do is to set up ErrFp to be stderr */ ErrFp = stderr; /* Set up global vars */ ArgC = argc; ArgV = argv; InitRemind(argc, argv); if (DoCalendar || (DoSimpleCalendar && (!NextMode || PsCal))) { ProduceCalendar(); 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) { ClearGlobalOmits(); DestroyOmitContexts(); DestroyVars(0); NumTriggered = 0; JulianToday++; } } return 0; } /***************************************************************/ /* */ /* DoReminders */ /* */ /* The normal case - we're not doing a calendar. */ /* */ /***************************************************************/ static void DoReminders(void) { int r; Token tok; char *s; Parser p; if (!UseStdin) { FileAccessDate = GetAccessDate(InitialFile); } else { FileAccessDate = JulianToday; } if (FileAccessDate < 0) { fprintf(ErrFp, "%s: `%s'.\n", ErrMsg[E_CANTACCESS], InitialFile); exit(1); } r=OpenFile(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 ***/ } else { /* Create a parser to parse the line */ CreateParser(s, &p); switch(tok.type) { case T_Empty: case T_Comment: break; case T_Rem: r=DoRem(&p); 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: r=DoInclude(&p); 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); } 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); break; } else { CreateParser(CurLine, &p); r=DoRem(&p); 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); r=DoRem(&p); break; } if (r && (!Hush || r != E_RUN_DISABLED)) { Eprint("%s", ErrMsg[r]); } /* 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. */ /* */ /***************************************************************/ 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); } *y = try_yr; *m = try_mon; *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(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->pos++; r = EvalExpr(&(p->pos), &val); 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 (isspace(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 && isspace(c)) { c = ParseChar(p, &err, 0); if (err) return err; } if (!c) return OK; while (c && !isspace(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 && isspace(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 (isspace(*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); if (r) return r; if (bracketed) { if (*p->pos != END_OF_EXPR) return E_MISS_END; (p->pos)++; } return OK; } /***************************************************************/ /* */ /* Eprint - print an error message. */ /* */ /***************************************************************/ void Eprint(const char *fmt, ...) { va_list argptr; /* Check if more than one error msg. from this line */ if (!FreshLine && !ShowAllErrors) return; if (FreshLine) { FreshLine = 0; if (strcmp(FileName, "-")) (void) fprintf(ErrFp, "%s(%d): ", FileName, LineNo); else (void) fprintf(ErrFp, "-stdin-(%d): ", LineNo); if (DebugFlag & DB_PRTLINE) OutputLine(ErrFp); } else 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) { register char *s = CurLine; register 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 *s, ParsePtr p) { p->text = s; p->pos = s; p->isnested = 0; p->epos = NULL; p->etext = NULL; p->allownested = 1; p->tokenPushed = NULL; DBufInit(&p->pushedToken); } /***************************************************************/ /* */ /* DestroyParser */ /* */ /* Destroy a parser, freeing up resources used. */ /* */ /***************************************************************/ void DestroyParser(ParsePtr p) { if (p->isnested && p->etext) { free(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(const char *tok, ParsePtr p) { DBufFree(&p->pushedToken); if (DBufPuts(&p->pushedToken, (char *) 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 (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; } 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; 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); 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; unsigned syndrome; Trigger trig; TimeTrig tim; int jul; if (NumIfs >= IF_NEST) return E_NESTED_IF; if (ShouldIgnoreLine()) syndrome = IF_TRUE | BEFORE_ELSE; else { if ( (r=ParseRem(p, &trig, &tim)) ) return r; if (trig.typ != NO_TYPE) return E_PARSE_ERR; jul = ComputeTrigger(trig.scanfrom, &trig, &r); if (r) syndrome = IF_TRUE | BEFORE_ELSE; else { if (ShouldTriggerReminder(&trig, &tim, jul)) syndrome = IF_TRUE | BEFORE_ELSE; else syndrome = IF_FALSE | BEFORE_ELSE; } } 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> (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; } } } /***************************************************************/ /* */ /* 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 (isspace(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; 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 *s; DynamicBuffer buf; 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 (isspace(*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-leapyear beginning on Wednesday, and FoldArray[1][5] is a leapyear 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; } /***************************************************************/ /* */ /* 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 *s) { int line = 0; int i, j; int doublespace = 1; int pendspace; int len; char *t; int roomleft; if (!s || !*s) return; /* Skip leading spaces */ while(ISBLANK(*s)) s++; /* 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= 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. */ /* */ /***************************************************************/ void SigIntHandler(int d) { signal(SIGINT, SigIntHandler); GotSigInt(); exit(0); }