/***************************************************************/ /* */ /* CALENDAR.C */ /* */ /* The code for generating a calendar. */ /* */ /* 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 "types.h" #include "protos.h" #include "expr.h" #include "globals.h" #include "err.h" #include "md5.h" /* Data structures used by the calendar */ typedef struct cal_entry { struct cal_entry *next; char *text; char *pos; int time; int priority; char tag[TAG_LEN+1]; char passthru[PASSTHRU_LEN+1]; int duration; char *filename; int lineno; } CalEntry; /* Global variables */ static CalEntry *CalColumn[7]; static int ColSpaces; static void SortCol (CalEntry **col); static void DoCalendarOneWeek (void); static void DoCalendarOneMonth (void); static int WriteCalendarRow (void); static void PrintLeft (char *s, int width, char pad); static void PrintCentered (char *s, int width, char pad); static int WriteOneCalLine (void); static int WriteOneColLine (int col); static void GenerateCalEntries (int col); static void WriteCalHeader (void); static void WriteCalTrailer (void); static int DoCalRem (ParsePtr p, int col); static void WriteSimpleEntries (int col, int jul); static void WriteSolidCalLine (void); static void WriteIntermediateCalLine (void); static void WriteCalDays (void); /***************************************************************/ /* */ /* ProduceCalendar */ /* */ /* Main loop for generating a calendar. */ /* */ /***************************************************************/ void ProduceCalendar(void) { int y, m, d; ShouldCache = 1; ColSpaces = (CalWidth - 9) / 7; CalWidth = 7*ColSpaces + 8; if (CalMonths) { FromJulian(JulianToday, &y, &m, &d); JulianToday = Julian(y, m, 1); while (CalMonths--) DoCalendarOneMonth(); return; } else { if (MondayFirst) JulianToday -= (JulianToday%7); else JulianToday -= ((JulianToday+1)%7); if (!DoSimpleCalendar) { WriteIntermediateCalLine(); WriteCalDays(); WriteIntermediateCalLine(); } while (CalWeeks--) DoCalendarOneWeek(); return; } } /***************************************************************/ /* */ /* DoCalendarOneWeek */ /* */ /* Write a calendar for a single week */ /* */ /***************************************************************/ static void DoCalendarOneWeek(void) { int y, m, d, done, i, l, wd; char buf[81]; int LinesWritten = 0; int OrigJul = JulianToday; /* Fill in the column entries */ for (i=0; i<7; i++) { GenerateCalEntries(i); JulianToday++; } /* Output the entries */ /* If it's "Simple Calendar" format, do it simply... */ if (DoSimpleCalendar) { if (MondayFirst) wd = JulianToday % 7; else wd = (JulianToday + 1) % 7; for (i=0; i<7; i++) { WriteSimpleEntries(i, OrigJul+i-wd); } return; } /* Here come the first few lines... */ PutChar('|'); for (i=0; i<7; i++) { FromJulian(OrigJul+i, &y, &m, &d); sprintf(buf, "%d %c%c%c ", d, MonthName[m][0], MonthName[m][1], MonthName[m][2]); if (OrigJul+i == RealToday) PrintLeft(buf, ColSpaces, '*'); else PrintLeft(buf, ColSpaces, ' '); PutChar('|'); } PutChar('\n'); for (l=0; l11) { mm = 0; yy = y+1; } else yy=y; printf("%s %d\n", MonthName[mm], DaysInMonth(mm,yy)); } while (WriteCalendarRow()) continue; if (PsCal) printf("%s\n", PSEND); if (!DoSimpleCalendar) WriteCalTrailer(); } /***************************************************************/ /* */ /* WriteCalendarRow */ /* */ /* Write one row of the calendar */ /* */ /***************************************************************/ static int WriteCalendarRow(void) { int y, m, d, wd, i, l; int done; char buf[81]; int OrigJul = JulianToday; int LinesWritten = 0; /* Get the date of the first day */ FromJulian(JulianToday, &y, &m, &d); if (!MondayFirst) wd = (JulianToday + 1) % 7; else wd = JulianToday % 7; /* Fill in the column entries */ for (i=wd; i<7; i++) { if (d+i-wd > DaysInMonth(m, y)) break; GenerateCalEntries(i); JulianToday++; } /* Output the entries */ /* If it's "Simple Calendar" format, do it simply... */ if (DoSimpleCalendar) { for (i=wd; i<7 && d+i-wd<=DaysInMonth(m, y); i++) { WriteSimpleEntries(i, OrigJul+i-wd); } return (d+7-wd <= DaysInMonth(m, y)); } /* Here come the first few lines... */ PutChar('|'); for (i=0; i<7; i++) { if (i < wd || d+i-wd>DaysInMonth(m, y)) PrintLeft("", ColSpaces, ' '); else { sprintf(buf, "%d", d+i-wd); PrintLeft(buf, ColSpaces, ' '); } PutChar('|'); } PutChar('\n'); for (l=0; lpos; /* If we're at the end, and there's another entry, do a blank line and move to next entry. */ if (!*s && e->next) { PrintLeft("", ColSpaces, ' '); CalColumn[col] = e->next; free(e->text); free(e->filename); free(e); return 1; } /* Find the last space char within the column. */ while (s - e->pos <= ColSpaces) { if (!*s) {space = s; break;} if (*s == ' ') space = s; s++; } /* If we couldn't find a space char, print what we have. */ if (!space) { for (s = e->pos; s - e->pos < ColSpaces; s++) { if (!*s) break; numwritten++; PutChar(*s); } e->pos = s; } else { /* We found a space - print everything before it. */ for (s = e->pos; snext) { CalColumn[col] = e->next; free(e->text); free(e->filename); free(e); } else { e->pos = s; } if (CalColumn[col]) return 1; else return 0; } /***************************************************************/ /* */ /* GenerateCalEntries */ /* */ /* Generate the calendar entries for the ith column */ /* */ /***************************************************************/ static void GenerateCalEntries(int col) { int r; Token tok; char *s; Parser p; /* Do some initialization first... */ ClearGlobalOmits(); DestroyOmitContexts(); DestroyVars(0); NumTriggered = 0; 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()) { /* DO NOTHING */ } else { /* Create a parser to parse the line */ CreateParser(s, &p); switch(tok.type) { case T_Empty: case T_Comment: break; case T_ErrMsg: r=DoErrMsg(&p); break; case T_Rem: r=DoCalRem(&p, col); 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_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_Flush: r=DoFlush(&p); break; case T_Debug: break; /* IGNORE DEBUG CMD */ case T_Dumpvars: break; /* IGNORE DUMPVARS CMD */ case T_Banner: break; /* IGNORE BANNER CMD */ case T_Omit: r=DoOmit(&p); if (r == E_PARSE_AS_REM) { DestroyParser(&p); CreateParser(s, &p); r=DoCalRem(&p, col); } break; case T_Pop: r=PopOmitContext(&p); break; case T_Push: r=PushOmitContext(&p); break; case T_Preserve: r=DoPreserve(&p); break; case T_RemType: if (tok.val == RUN_TYPE) { r=DoRun(&p); break; } else { CreateParser(CurLine, &p); r=DoCalRem(&p, col); 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=DoCalRem(&p, col); 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); } } } /***************************************************************/ /* */ /* WriteCalHeader */ /* */ /***************************************************************/ static void WriteCalHeader(void) { char buf[80]; int y, m, d; FromJulian(JulianToday, &y, &m, &d); sprintf(buf, "%s %d", MonthName[m], y); WriteSolidCalLine(); PutChar('|'); PrintCentered(buf, CalWidth-2, ' '); PutChar('|'); PutChar('\n'); WriteIntermediateCalLine(); WriteCalDays(); WriteIntermediateCalLine(); } /***************************************************************/ /* */ /* WriteCalTrailer */ /* */ /***************************************************************/ static void WriteCalTrailer(void) { PutChar('\f'); } /***************************************************************/ /* */ /* DoCalRem */ /* */ /* Do the REM command in the context of a calendar. */ /* */ /***************************************************************/ static int DoCalRem(ParsePtr p, int col) { int oldLen; Trigger trig; TimeTrig tim; Value v; int r; int jul; CalEntry *CurCol = CalColumn[col]; CalEntry *e; char *s, *s2; DynamicBuffer buf, obuf, pre_buf; Token tok; DBufInit(&buf); DBufInit(&pre_buf); /* Parse the trigger date and time */ if ( (r=ParseRem(p, &trig, &tim)) ) return r; /* Don't include timed reminders in calendar if -a option supplied. */ if (DontIssueAts && tim.ttime != NO_TIME) return OK; if (trig.typ == NO_TYPE) return E_EOLN; if (trig.typ == SAT_TYPE) { r=DoSatRemind(&trig, &tim, p); if (r) return r; r=ParseToken(p, &buf); if (r) return r; FindToken(DBufValue(&buf), &tok); DBufFree(&buf); if (tok.type == T_Empty || tok.type == T_Comment) return OK; if (tok.type != T_RemType || tok.val == SAT_TYPE) return E_PARSE_ERR; if (tok.val == PASSTHRU_TYPE) { r=ParseToken(p, &buf); if (r) return r; if (!DBufLen(&buf)) { DBufFree(&buf); return E_EOLN; } StrnCpy(trig.passthru, DBufValue(&buf), PASSTHRU_LEN); DBufFree(&buf); } trig.typ = tok.val; jul = LastTriggerDate; if (!LastTrigValid) return OK; } else { /* Calculate the trigger date */ jul = ComputeTrigger(trig.scanfrom, &trig, &r); if (r) return r; } /* Convert PS and PSF to PASSTHRU */ if (trig.typ == PS_TYPE) { strcpy(trig.passthru, "PostScript"); trig.typ = PASSTHRU_TYPE; } else if (trig.typ == PSF_TYPE) { strcpy(trig.passthru, "PSFile"); trig.typ = PASSTHRU_TYPE; } if (trig.typ == PASSTHRU_TYPE) { if (!PsCal && strcmp(trig.passthru, "COLOR")) return OK; if (!strcmp(trig.passthru, "COLOR")) { /* Strip off the three color numbers */ DBufFree(&buf); r=ParseToken(p, &buf); DBufPuts(&pre_buf, DBufValue(&buf)); DBufPutc(&pre_buf, ' '); DBufFree(&buf); if (r) return r; r=ParseToken(p, &buf); DBufPuts(&pre_buf, DBufValue(&buf)); DBufPutc(&pre_buf, ' '); DBufFree(&buf); if (r) return r; r=ParseToken(p, &buf); DBufPuts(&pre_buf, DBufValue(&buf)); DBufPutc(&pre_buf, ' '); DBufFree(&buf); if (r) return r; if (!PsCal && !DoSimpleCalendar) { DBufFree(&pre_buf); } } } /* If trigger date == today, add it to the current entry */ DBufInit(&obuf); if ((jul == JulianToday) || (DoSimpleCalendar && DoSimpleCalDelta && ShouldTriggerReminder(&trig, &tim, jul))) { NumTriggered++; if (DoSimpleCalendar || tim.ttime != NO_TIME) { /* Suppress time if it's not today or if it's a non-COLOR special */ if (jul != JulianToday || (trig.typ == PASSTHRU_TYPE && strcmp(trig.passthru, "COLOR"))) { if (DBufPuts(&obuf, SimpleTime(NO_TIME)) != OK) { DBufFree(&obuf); DBufFree(&pre_buf); return E_NO_MEM; } } else { if (DBufPuts(&obuf, CalendarTime(tim.ttime, tim.duration)) != OK) { DBufFree(&obuf); DBufFree(&pre_buf); return E_NO_MEM; } } } if (trig.typ != PASSTHRU_TYPE && UserFuncExists("calprefix")==1) { char evalBuf[64]; sprintf(evalBuf, "calprefix(%d)", trig.priority); s2 = evalBuf; r = EvalExpr(&s2, &v); if (!r) { if (!DoCoerce(STR_TYPE, &v)) { if (DBufPuts(&obuf, v.v.str) != OK) { DestroyValue(v); DBufFree(&obuf); DBufFree(&pre_buf); return E_NO_MEM; } } DestroyValue(v); } } oldLen = DBufLen(&obuf); /* In -sa mode, run in ADVANCE mode if we're triggering * before the actual date */ if (jul != JulianToday) { r = DoSubst(p, &obuf, &trig, &tim, jul, ADVANCE_MODE); } else { r = DoSubst(p, &obuf, &trig, &tim, jul, CAL_MODE); } if (r) { DBufFree(&pre_buf); DBufFree(&obuf); return r; } if (DBufLen(&obuf) <= oldLen) { DBufFree(&obuf); DBufFree(&pre_buf); return OK; } if (trig.typ != PASSTHRU_TYPE && UserFuncExists("calsuffix")==1) { char evalBuf[64]; sprintf(evalBuf, "calsuffix(%d)", trig.priority); s2 = evalBuf; r = EvalExpr(&s2, &v); if (!r) { if (!DoCoerce(STR_TYPE, &v)) { if (DBufPuts(&obuf, v.v.str) != OK) { DestroyValue(v); DBufFree(&obuf); DBufFree(&pre_buf); return E_NO_MEM; } } DestroyValue(v); } } s = DBufValue(&obuf); if (!DoSimpleCalendar) while (isspace(*s)) s++; DBufPuts(&pre_buf, s); s = DBufValue(&pre_buf); e = NEW(CalEntry); if (!e) { DBufFree(&obuf); DBufFree(&pre_buf); return E_NO_MEM; } e->text = StrDup(s); DBufFree(&obuf); DBufFree(&pre_buf); if (!e->text) { free(e); return E_NO_MEM; } StrnCpy(e->tag, trig.tag, TAG_LEN); if (!e->tag[0]) { if (SynthesizeTags) { SynthesizeTag(e->tag); } else { strcpy(e->tag, "*"); } } e->duration = tim.duration; e->priority = trig.priority; e->filename = StrDup(FileName); if(!e->filename) { free(e); return E_NO_MEM; } e->lineno = LineNo; if (trig.typ == PASSTHRU_TYPE) { StrnCpy(e->passthru, trig.passthru, PASSTHRU_LEN); } else { e->passthru[0] = 0; } e->pos = e->text; if (jul == JulianToday) { e->time = tim.ttime; } else { e->time = NO_TIME; } e->next = CurCol; CalColumn[col] = e; SortCol(&CalColumn[col]); } return OK; } /***************************************************************/ /* */ /* WriteSimpleEntries */ /* */ /* Write entries in 'simple calendar' format. */ /* */ /***************************************************************/ static void WriteSimpleEntries(int col, int jul) { CalEntry *e = CalColumn[col]; CalEntry *n; int y, m, d; FromJulian(jul, &y, &m, &d); while(e) { if (DoPrefixLineNo) printf("# fileinfo %d %s\n", e->lineno, e->filename); printf("%04d/%02d/%02d", y, m+1, d); if (e->passthru[0]) { printf(" %s", e->passthru); } else { printf(" *"); } printf(" %s ", e->tag); if (e->duration != NO_TIME) { printf("%d ", e->duration); } else { printf("* "); } if (e->time != NO_TIME) { printf("%d ", e->time); } else { printf("* "); } printf("%s\n", e->text); free(e->text); free(e->filename); n = e->next; free(e); e = n; } CalColumn[col] = NULL; } /***************************************************************/ /* */ /* Various functions for writing different types of lines. */ /* */ /***************************************************************/ static void WriteSolidCalLine(void) { PutChar('+'); PrintCentered("", CalWidth-2, '-'); PutChar('+'); PutChar('\n'); } static void WriteIntermediateCalLine(void) { int i; PutChar('+'); for (i=0; i<7; i++) { PrintCentered("", ColSpaces, '-'); PutChar('+'); } PutChar('\n'); } static void WriteCalDays(void) { int i; PutChar('|'); for (i=0; i<7; i++) { if (!MondayFirst) PrintCentered(DayName[(i+6)%7], ColSpaces, ' '); else PrintCentered(DayName[i%7], ColSpaces, ' '); PutChar('|'); } PutChar('\n'); } /***************************************************************/ /* */ /* CalendarTime */ /* */ /* Format the time according to simple time format. */ /* Answer is returned in a static buffer. */ /* A trailing space is always added. */ /* This takes into account duration */ /* */ /***************************************************************/ char * CalendarTime(int tim, int duration) { static char buf[128]; int h, min, hh; int h2, min2, hh2, newtim, days; char *ampm1; char *ampm2; char daybuf[64]; buf[0] = 0; if (duration == NO_TIME) { /* No duration... just call into SimpleTime */ return SimpleTime(tim); } if (tim == NO_TIME) { /* No time... nothing to return */ return buf; } h = tim/60; min = tim % 60; if (h == 0) hh=12; else if (h > 12) hh=h-12; else hh = h; newtim = tim + duration; /* How many days in duration? */ days = newtim / MINUTES_PER_DAY; newtim = newtim % MINUTES_PER_DAY; h2 = newtim/60; min2 = newtim % 60; if (h2 == 0) hh2=12; else if (h2 > 12) hh2=h2-12; else hh2 = h2; if (days) { sprintf(daybuf, "+%d", days); } else { daybuf[0] = 0; } if (h >= 12) { ampm1 = L_PM; } else { ampm1 = L_AM; } if (h2 >= 12) { ampm2 = L_PM; } else { ampm2 = L_AM; } if (!days) { if (!strcmp(ampm1, ampm2)) { ampm1 = ""; } } switch(ScFormat) { case SC_AMPM: sprintf(buf, "%d%c%02d%s-%d%c%02d%s%s ", hh, TimeSep, min, ampm1, hh2, TimeSep, min2, ampm2, daybuf); break; case SC_MIL: sprintf(buf, "%02d%c%02d-%02d%c%02d%s ", h, TimeSep, min, h2, TimeSep, min2, daybuf); break; } return buf; } /***************************************************************/ /* */ /* SimpleTime */ /* */ /* Format the time according to simple time format. */ /* Answer is returned in a static buffer. */ /* A trailing space is always added. */ /* */ /***************************************************************/ char *SimpleTime(int tim) { static char buf[32]; int h, min, hh; buf[0] = 0; switch(ScFormat) { case SC_AMPM: if (tim != NO_TIME) { h = tim / 60; min = tim % 60; if (h == 0) hh=12; else if (h > 12) hh=h-12; else hh=h; sprintf(buf, "%d%c%02d%s ", hh, TimeSep, min, (h>=12) ? L_PM : L_AM); } break; case SC_MIL: if (tim != NO_TIME) { h = tim / 60; min = tim % 60; sprintf(buf, "%02d%c%02d ", h, TimeSep, min); } break; } return buf; } /***************************************************************/ /* */ /* SortCol */ /* */ /* Sort the calendar entries in a column by time and priority */ /* */ /***************************************************************/ static void SortCol(CalEntry **col) { CalEntry *cur, *prev, *next; cur = *col; prev = NULL; /* Note that we use <= comparison rather than > comparison to preserve the file order of reminders which have the same time and priority */ while (cur->next && CompareRems(0, cur->time, cur->priority, 0, cur->next->time, cur->next->priority, SortByDate, SortByTime, SortByPrio) <= 0) { next = cur->next; /* Swap cur and next */ if (!prev) { *col = next; cur->next = next->next; next->next = cur; prev = next; } else { prev->next = next; cur->next = next->next; next->next = cur; prev = next; } } } void SynthesizeTag(char *out) { struct MD5Context ctx; unsigned char buf[16]; MD5Init(&ctx); MD5Update(&ctx, (unsigned char *) CurLine, strlen(CurLine)); MD5Final(buf, &ctx); sprintf(out, "__syn__%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", (unsigned int) buf[0], (unsigned int) buf[1], (unsigned int) buf[2], (unsigned int) buf[3], (unsigned int) buf[4], (unsigned int) buf[5], (unsigned int) buf[6], (unsigned int) buf[7], (unsigned int) buf[8], (unsigned int) buf[9], (unsigned int) buf[10], (unsigned int) buf[11], (unsigned int) buf[12], (unsigned int) buf[13], (unsigned int) buf[14], (unsigned int) buf[15]); }