Files
remind/src/calendar.c
Dianne Skoll 6aa4d681e8 Refactor code.
2026-01-09 17:07:55 -05:00

3066 lines
87 KiB
C

/***************************************************************/
/* */
/* CALENDAR.C */
/* */
/* The code for generating a calendar. */
/* */
/* This file is part of REMIND. */
/* Copyright (C) 1992-2026 by Dianne Skoll */
/* SPDX-License-Identifier: GPL-2.0-only */
/* */
/***************************************************************/
#define _XOPEN_SOURCE 600
#include "config.h"
#include "custom.h"
#include <stdio.h>
#include <string.h>
#include <strings.h>
#include <ctype.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <wctype.h>
#include <wchar.h>
#ifdef HAVE_LANGINFO_H
#include <langinfo.h>
#endif
#include "types.h"
#include "protos.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 *raw_text;
char const *pos;
wchar_t *wc_text;
wchar_t const *wc_pos;
int is_color;
int r, g, b;
int time;
int priority;
DynamicBuffer tags;
char passthru[PASSTHRU_LEN+1];
int duration;
char const *filename;
int lineno;
int lineno_start;
Trigger trig;
TimeTrig tt;
int nonconst_expr;
int if_depth;
TrigInfo *infos;
} CalEntry;
/* Line-drawing sequences */
struct line_drawing {
char const *graphics_on;
char const *graphics_off;
char *tlr, *bl, *tbl, *blr, *tblr, *tr, *tb, *br, *tbr, *tl, *lr;
};
static struct line_drawing NormalDrawing = {
"", "", "+", "+", "+", "+", "+", "+", "|", "+", "+", "+", "-"
};
static struct line_drawing VT100Drawing = {
"\x1B(0", "\x1B(B",
"\x76", "\x6b", "\x75", "\x77", "\x6e", "\x6d", "\x78",
"\x6c", "\x74", "\x6a", "\x71"
};
static int encoding_is_utf8 = 0;
static struct line_drawing UTF8Drawing = {
"", "",
"\xe2\x94\xb4",
"\xe2\x94\x90",
"\xe2\x94\xa4",
"\xe2\x94\xac",
"\xe2\x94\xbc",
"\xe2\x94\x94",
"\xe2\x94\x82",
"\xe2\x94\x8c",
"\xe2\x94\x9c",
"\xe2\x94\x98",
"\xe2\x94\x80"
};
static char *VT100Colors[2][2][2][2] /* [Br][R][G][B] */ = {
{
/*** DIM COLORS ***/
{
{
/* 0, 0, 0 = Black */ "\x1B[0;30m",
/* 0, 0, 1 = Blue */ "\x1B[0;34m"
},
{
/* 0, 1, 0 = Green */ "\x1B[0;32m",
/* 0, 1, 1 = Cyan */ "\x1B[0;36m"
}
},
{
{
/* 1, 0, 0 = Red */ "\x1B[0;31m",
/* 1, 0, 1 = Magenta */ "\x1B[0;35m"
},
{
/* 1, 1, 0 = Yellow */ "\x1B[0;33m",
/* 1, 1, 1 = White */ "\x1B[0;37m"
}
}
},
{
/*** BRIGHT COLORS ***/
{
{
/* 0, 0, 0 = Grey */ "\x1B[30;1m",
/* 0, 0, 1 = Blue */ "\x1B[34;1m"
},
{
/* 0, 1, 0 = Green */ "\x1B[32;1m",
/* 0, 1, 1 = Cyan */ "\x1B[36;1m"
}
},
{
{
/* 1, 0, 0 = Red */ "\x1B[31;1m",
/* 1, 0, 1 = Magenta */ "\x1B[35;1m"
},
{
/* 1, 1, 0 = Yellow */ "\x1B[33;1m",
/* 1, 1, 1 = White */ "\x1B[37;1m"
}
}
}
};
static char *VT100BGColors[2][2][2] /* [R][G][B] */ = {
{
{
/* 0, 0, 0 = Black */ "\x1B[0;40m",
/* 0, 0, 1 = Blue */ "\x1B[0;44m"
},
{
/* 0, 1, 0 = Green */ "\x1B[0;42m",
/* 0, 1, 1 = Cyan */ "\x1B[0;46m"
}
},
{
{
/* 1, 0, 0 = Red */ "\x1B[0;41m",
/* 1, 0, 1 = Magenta */ "\x1B[0;45m"
},
{
/* 1, 1, 0 = Yellow */ "\x1B[0;43m",
/* 1, 1, 1 = White */ "\x1B[0;47m"
}
}
};
/* Moon phase icons in UTF-8 */
static char const *moonphase_emojis[] = {
"\xF0\x9F\x8C\x91",
"\xF0\x9F\x8C\x93",
"\xF0\x9F\x8C\x95",
"\xF0\x9F\x8C\x97"
};
/* Color weights for gamma-correction */
unsigned char cwts[] = {
0x46, 0x75, 0x63, 0x6b, 0x20, 0x74, 0x68, 0x6f, 0x73, 0x65, 0x20,
0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x68, 0x6f, 0x62, 0x69, 0x63,
0x20, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x6e, 0x20, 0x52,
0x65, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x61, 0x6e, 0x73, 0x2e,
0x20, 0x20, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x20, 0x72, 0x69, 0x67,
0x68, 0x74, 0x73, 0x20, 0x61, 0x72, 0x65, 0x20, 0x68, 0x75, 0x6d,
0x61, 0x6e, 0x20, 0x72, 0x69, 0x67, 0x68, 0x74, 0x73, 0x2e
};
/* Moon phases for each day 1-31, up to 32 chars per moon-phase string
including termination \0 */
static char moons[32][32];
/* Week indicators */
static char weeks[32][32];
/* Background colors of each day 1-31, rgb */
static int bgcolor[32][3];
static struct line_drawing *linestruct;
#define DRAW(x) fputs(linestruct->x, stdout)
struct xterm256_colors {
int r;
int g;
int b;
};
/* Xterm support 256 different colors, numbered from 0 to 255. The following
table is a list of the [r, g, b] components of each Xterm color. The
data was obtained from https://jonasjacek.github.io/colors/ */
static struct xterm256_colors XTerm256Colors[] =
{
{ 0, 0, 0}, {128, 0, 0}, { 0, 128, 0}, {128, 128, 0},
{ 0, 0, 128}, {128, 0, 128}, { 0, 128, 128}, {192, 192, 192},
{128, 128, 128}, {255, 0, 0}, { 0, 255, 0}, {255, 255, 0},
{ 0, 0, 255}, {255, 0, 255}, { 0, 255, 255}, {255, 255, 255},
{ 0, 0, 0}, { 0, 0, 95}, { 0, 0, 135}, { 0, 0, 175},
{ 0, 0, 215}, { 0, 0, 255}, { 0, 95, 0}, { 0, 95, 95},
{ 0, 95, 135}, { 0, 95, 175}, { 0, 95, 215}, { 0, 95, 255},
{ 0, 135, 0}, { 0, 135, 95}, { 0, 135, 135}, { 0, 135, 175},
{ 0, 135, 215}, { 0, 135, 255}, { 0, 175, 0}, { 0, 175, 95},
{ 0, 175, 135}, { 0, 175, 175}, { 0, 175, 215}, { 0, 175, 255},
{ 0, 215, 0}, { 0, 215, 95}, { 0, 215, 135}, { 0, 215, 175},
{ 0, 215, 215}, { 0, 215, 255}, { 0, 255, 0}, { 0, 255, 95},
{ 0, 255, 135}, { 0, 255, 175}, { 0, 255, 215}, { 0, 255, 255},
{ 95, 0, 0}, { 95, 0, 95}, { 95, 0, 135}, { 95, 0, 175},
{ 95, 0, 215}, { 95, 0, 255}, { 95, 95, 0}, { 95, 95, 95},
{ 95, 95, 135}, { 95, 95, 175}, { 95, 95, 215}, { 95, 95, 255},
{ 95, 135, 0}, { 95, 135, 95}, { 95, 135, 135}, { 95, 135, 175},
{ 95, 135, 215}, { 95, 135, 255}, { 95, 175, 0}, { 95, 175, 95},
{ 95, 175, 135}, { 95, 175, 175}, { 95, 175, 215}, { 95, 175, 255},
{ 95, 215, 0}, { 95, 215, 95}, { 95, 215, 135}, { 95, 215, 175},
{ 95, 215, 215}, { 95, 215, 255}, { 95, 255, 0}, { 95, 255, 95},
{ 95, 255, 135}, { 95, 255, 175}, { 95, 255, 215}, { 95, 255, 255},
{135, 0, 0}, {135, 0, 95}, {135, 0, 135}, {135, 0, 175},
{135, 0, 215}, {135, 0, 255}, {135, 95, 0}, {135, 95, 95},
{135, 95, 135}, {135, 95, 175}, {135, 95, 215}, {135, 95, 255},
{135, 135, 0}, {135, 135, 95}, {135, 135, 135}, {135, 135, 175},
{135, 135, 215}, {135, 135, 255}, {135, 175, 0}, {135, 175, 95},
{135, 175, 135}, {135, 175, 175}, {135, 175, 215}, {135, 175, 255},
{135, 215, 0}, {135, 215, 95}, {135, 215, 135}, {135, 215, 175},
{135, 215, 215}, {135, 215, 255}, {135, 255, 0}, {135, 255, 95},
{135, 255, 135}, {135, 255, 175}, {135, 255, 215}, {135, 255, 255},
{175, 0, 0}, {175, 0, 95}, {175, 0, 135}, {175, 0, 175},
{175, 0, 215}, {175, 0, 255}, {175, 95, 0}, {175, 95, 95},
{175, 95, 135}, {175, 95, 175}, {175, 95, 215}, {175, 95, 255},
{175, 135, 0}, {175, 135, 95}, {175, 135, 135}, {175, 135, 175},
{175, 135, 215}, {175, 135, 255}, {175, 175, 0}, {175, 175, 95},
{175, 175, 135}, {175, 175, 175}, {175, 175, 215}, {175, 175, 255},
{175, 215, 0}, {175, 215, 95}, {175, 215, 135}, {175, 215, 175},
{175, 215, 215}, {175, 215, 255}, {175, 255, 0}, {175, 255, 95},
{175, 255, 135}, {175, 255, 175}, {175, 255, 215}, {175, 255, 255},
{215, 0, 0}, {215, 0, 95}, {215, 0, 135}, {215, 0, 175},
{215, 0, 215}, {215, 0, 255}, {215, 95, 0}, {215, 95, 95},
{215, 95, 135}, {215, 95, 175}, {215, 95, 215}, {215, 95, 255},
{215, 135, 0}, {215, 135, 95}, {215, 135, 135}, {215, 135, 175},
{215, 135, 215}, {215, 135, 255}, {215, 175, 0}, {215, 175, 95},
{215, 175, 135}, {215, 175, 175}, {215, 175, 215}, {215, 175, 255},
{215, 215, 0}, {215, 215, 95}, {215, 215, 135}, {215, 215, 175},
{215, 215, 215}, {215, 215, 255}, {215, 255, 0}, {215, 255, 95},
{215, 255, 135}, {215, 255, 175}, {215, 255, 215}, {215, 255, 255},
{255, 0, 0}, {255, 0, 95}, {255, 0, 135}, {255, 0, 175},
{255, 0, 215}, {255, 0, 255}, {255, 95, 0}, {255, 95, 95},
{255, 95, 135}, {255, 95, 175}, {255, 95, 215}, {255, 95, 255},
{255, 135, 0}, {255, 135, 95}, {255, 135, 135}, {255, 135, 175},
{255, 135, 215}, {255, 135, 255}, {255, 175, 0}, {255, 175, 95},
{255, 175, 135}, {255, 175, 175}, {255, 175, 215}, {255, 175, 255},
{255, 215, 0}, {255, 215, 95}, {255, 215, 135}, {255, 215, 175},
{255, 215, 215}, {255, 215, 255}, {255, 255, 0}, {255, 255, 95},
{255, 255, 135}, {255, 255, 175}, {255, 255, 215}, {255, 255, 255},
{ 8, 8, 8}, { 18, 18, 18}, { 28, 28, 28}, { 38, 38, 38},
{ 48, 48, 48}, { 58, 58, 58}, { 68, 68, 68}, { 78, 78, 78},
{ 88, 88, 88}, { 98, 98, 98}, {108, 108, 108}, {118, 118, 118},
{128, 128, 128}, {138, 138, 138}, {148, 148, 148}, {158, 158, 158},
{168, 168, 168}, {178, 178, 178}, {188, 188, 188}, {198, 198, 198},
{208, 208, 208}, {218, 218, 218}, {228, 228, 228}, {238, 238, 238}
};
/* Global variables */
static CalEntry *CalColumn[7];
static int ColToDay[7];
static int ColSpaces;
static int DidAMonth;
static int DidAWeek;
static int DidADay;
static char const *CalendarTime(int tim, int duration);
static void ColorizeEntry(CalEntry const *e, int clamp);
static void SortCol (CalEntry **col);
static void DoCalendarOneWeek (int nleft);
static void DoCalendarOneMonth (void);
static void DoSimpleCalendarOneMonth (void);
static int WriteCalendarRow (void);
static void WriteWeekHeaderLine (void);
static void WritePostHeaderLine (void);
static void PrintLeft (char const *s, int width, char pad);
static void PrintCentered (char const *s, int width, char const *pad);
static int WriteOneCalLine (int dse, int wd);
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 dse);
static void WriteTopCalLine (void);
static void WriteBottomCalLine (void);
static void WriteIntermediateCalLine (void);
static void WriteCalDays (void);
static char const *get_url(TrigInfo *infos);
static int
DayOf(int dse)
{
int d;
FromDSE(dse, NULL, NULL, &d);
return d;
}
static void
Backgroundize(int d)
{
if (d < 1 || d > 31) {
return;
}
if (cwts[d] == 0) {
return;
}
if (!UseBGVTColors) {
return;
}
if (bgcolor[d][0] < 0) {
return;
}
printf("%s", Colorize(bgcolor[d][0], bgcolor[d][1], bgcolor[d][2], 1, 0));
}
static void
UnBackgroundize(int d)
{
if (d < 1 || d > 31) {
return;
}
if (!UseBGVTColors) {
return;
}
if (bgcolor[d][0] < 0) {
return;
}
printf("%s", Decolorize());
}
static void
send_lrm(void)
{
/* Don't send LRM if SuppressLRM is set */
if (SuppressLRM) {
return;
}
/* Send a lrm control sequence if UseUTF8Chars is enabled
or char encoding is UTF-8
*/
if (UseUTF8Chars || encoding_is_utf8) {
printf("\xE2\x80\x8E");
}
}
static char const *
despace(char const *s)
{
static char buf[256];
char *t = buf;
if (strlen(s) > sizeof(buf)-1) {
/* Punt. :( */
return s;
}
while (*s) {
if (isspace(*s)) {
*t++ = '_';
} else {
*t++ = *s;
}
s++;
}
*t = 0;
return buf;
}
static void PrintJSONChar(char c) {
switch(c) {
case '\b': printf("\\b"); break;
case '\f': printf("\\f"); break;
case '\n': printf("\\n"); break;
case '\r': printf("\\r"); break;
case '\t': printf("\\t"); break;
case '"': printf("\\\""); break;
case '\\': printf("\\\\"); break;
default:
if ((c > 0 && c < 32) || c == 0x7f) {
printf("\\u%04x", (unsigned int) c);
} else {
printf("%c", c);
}
break;
}
}
void PrintJSONString(char const *s)
{
while (*s) {
switch(*s) {
case '\b': printf("\\b"); break;
case '\f': printf("\\f"); break;
case '\n': printf("\\n"); break;
case '\r': printf("\\r"); break;
case '\t': printf("\\t"); break;
case '"': printf("\\\""); break;
case '\\': printf("\\\\"); break;
default:
if ((*s > 0 && *s < 32) || *s == 0x7f) {
printf("\\u%04x", (unsigned int) *s);
} else {
printf("%c", *s);
}
break;
}
s++;
}
}
static void PrintJSONStringLC(char const *s)
{
while (*s) {
switch(*s) {
case '\b': printf("\\b"); break;
case '\f': printf("\\f"); break;
case '\n': printf("\\n"); break;
case '\r': printf("\\r"); break;
case '\t': printf("\\t"); break;
case '"': printf("\\\""); break;
case '\\': printf("\\\\"); break;
default:
if ((*s > 0 && *s < 32) || *s == 0x7f) {
printf("\\u%04x", (unsigned int) *s);
} else {
printf("%c", tolower(*s));
}
break;
}
s++;
}
}
void PrintJSONKeyPairInt(char const *name, int val)
{
printf("\"");
PrintJSONString(name);
printf("\":%d,", val);
}
void PrintJSONKeyPairString(char const *name, char const *val)
{
/* If value is blank, skip it! */
if (!val || !*val) {
return;
}
printf("\"");
PrintJSONString(name);
printf("\":\"");
PrintJSONString(val);
printf("\",");
}
static void PrintJSONKeyPairDate(char const *name, int dse)
{
int y, m, d;
if (dse == NO_DATE) {
/* Skip it! */
return;
}
FromDSE(dse, &y, &m, &d);
printf("\"");
PrintJSONString(name);
printf("\":\"%04d-%02d-%02d\",", y, m+1, d);
}
static void PrintJSONKeyPairDateTime(char const *name, int dt)
{
int y, m, d, h, i, k;
if (dt == NO_TIME) {
/* Skip it! */
return;
}
i = dt / MINUTES_PER_DAY;
FromDSE(i, &y, &m, &d);
k = dt % MINUTES_PER_DAY;
h = k / 60;
i = k % 60;
printf("\"");
PrintJSONString(name);
printf("\":\"%04d-%02d-%02dT%02d:%02d\",", y, m+1, d, h, i);
}
static void PrintJSONKeyPairTime(char const *name, int t)
{
int h, i;
if (t == NO_TIME) {
/* Skip it! */
return;
}
h = t / 60;
i = t % 60;
printf("\"");
PrintJSONString(name);
printf("\":\"%02d:%02d\",", h, i);
}
void PutWideChar(wchar_t const wc, DynamicBuffer *output)
{
char buf[MB_CUR_MAX+1];
int len;
len = wctomb(buf, wc);
if (len > 0) {
buf[len] = 0;
if (output) {
DBufPuts(output, buf);
} else {
fputs(buf, stdout);
}
}
}
static char const *
get_month_abbrev(char const *mon)
{
static char buf[80];
char *s;
wchar_t tmp_buf[128] = {0};
wchar_t *ws;
int i;
int len;
*buf = 0;
(void) mbstowcs(tmp_buf, mon, 127);
ws = tmp_buf;
s = buf;
for (i=0; i<3; i++) {
if (*ws) {
len = wctomb(s, *ws);
s += len;
if (wcwidth(*ws) == 0) {
i--;
}
ws++;
} else {
break;
}
}
*s = 0;
return buf;
}
static int make_wchar_versions(CalEntry *e)
{
size_t len;
wchar_t *buf;
len = mbstowcs(NULL, e->text, 0);
if (len == (size_t) -1) return 0;
buf = calloc(len+1, sizeof(wchar_t));
if (!buf) return 0;
(void) mbstowcs(buf, e->text, len+1);
buf[len] = 0;
e->wc_text = buf;
e->wc_pos = buf;
return 1;
}
static void gon(void)
{
printf("%s", linestruct->graphics_on);
}
static void goff(void)
{
printf("%s", linestruct->graphics_off);
}
static void
ClampColor(int *r, int *g, int *b)
{
if (GetTerminalBackground() == TERMINAL_BACKGROUND_UNKNOWN) {
/* No special clamping if terminal background is unknown */
return;
}
if (GetTerminalBackground() == TERMINAL_BACKGROUND_DARK) {
if (*r <= 64 && *g <= 64 && *b <= 64) {
int max = *r;
double factor;
if (*g > max) max = *g;
if (*b > max) max = *b;
if (max == 0) {
*r = 65;
*g = 65;
*b = 65;
return;
}
factor = 65.0 / (double) max;
*r = (int) (factor * (double) *r);
*g = (int) (factor * (double) *g);
*b = (int) (factor * (double) *b);
}
return;
}
if (GetTerminalBackground() == TERMINAL_BACKGROUND_LIGHT) {
if (*r > 191 && *g > 191 && *b > 191) {
int min = *r;
if (*g < min) min = *g;
if (*b < min) min = *b;
double factor = 192.0 / (double) min;
*r = (int) (factor * (double) *r);
*g = (int) (factor * (double) *g);
*b = (int) (factor * (double) *b);
}
}
}
char const *
Decolorize(void)
{
return "\x1B[0m";
}
static char const *
Colorize256(int r, int g, int b, int bg, int clamp)
{
static char buf[40];
int best = -1;
int best_dist = 0;
int dist;
struct xterm256_colors const *cur;
size_t i;
if (clamp) {
ClampColor(&r, &g, &b);
}
for (i=0; i<(sizeof(XTerm256Colors) / sizeof(XTerm256Colors[0])); i++) {
cur = &XTerm256Colors[i];
dist = ((r - cur->r) * (r - cur->r)) +
((b - cur->b) * (b - cur->b)) +
((g - cur->g) * (g - cur->g));
if (best == -1 || dist < best_dist) {
best_dist = dist;
best = (int) i;
}
}
if (bg) {
snprintf(buf, sizeof(buf), "\x1B[48;5;%dm", best);
} else {
snprintf(buf, sizeof(buf), "\x1B[38;5;%dm", best);
}
return buf;
}
static char const *
ColorizeTrue(int r, int g, int b, int bg, int clamp)
{
static char buf[40];
if (clamp) {
ClampColor(&r, &g, &b);
}
if (bg) {
snprintf(buf, sizeof(buf), "\x1B[48;2;%d;%d;%dm", r, g, b);
} else {
snprintf(buf, sizeof(buf), "\x1B[38;2;%d;%d;%dm", r, g, b);
}
return buf;
}
char const *
Colorize(int r, int g, int b, int bg, int clamp)
{
int bright = 0;
if (UseTrueColors) {
return ColorizeTrue(r, g, b, bg, clamp);
}
if (Use256Colors) {
return Colorize256(r, g, b, bg, clamp);
}
if (r > 128 || g > 128 || b > 128) {
bright = 1;
}
if (r > 64) r = 1;
else r = 0;
if (g > 64) g = 1;
else g = 0;
if (b > 64) b = 1;
else b = 0;
if (clamp && GetTerminalBackground() == TERMINAL_BACKGROUND_DARK && !bg) {
/* Convert black-on-black to grey */
if (!r && !g && !b) return VT100Colors[1][0][0][0];
}
if (clamp && GetTerminalBackground() == TERMINAL_BACKGROUND_LIGHT && !bg) {
/* Convert white-on-white to grey */
if (r && g && b) return VT100Colors[1][0][0][0];
}
if (bg) {
return VT100BGColors[r][g][b];
} else {
return VT100Colors[bright][r][g][b];
}
}
static void ColorizeEntry(CalEntry const *e, int clamp)
{
printf("%s", Colorize(e->r, e->g, e->b, 0, clamp));
}
static void
InitMoonsAndShades(void)
{
int i;
/* Initialize the moon array */
if (encoding_is_utf8) {
for (i=0; i<=31; i++) {
moons[i][0] = 0;
}
}
/* Clear SHADEs */
if (UseBGVTColors) {
for (i=0; i<=31; i++) {
bgcolor[i][0] = -1;
bgcolor[i][1] = -1;
bgcolor[i][2] = -1;
}
}
/* Clear weeks */
for(i=0; i<=31; i++) {
weeks[i][0] = 0;
}
}
static void
SetShadeEntry(int dse, char const *shade)
{
int d;
int r, g, b;
/* Don't bother if we're not doing SHADE specials */
if (!UseBGVTColors) {
return;
}
if (sscanf(shade, "%d %d %d", &r, &g, &b) != 3) {
if (sscanf(shade, "%d", &r) != 1) {
return;
}
g = r;
b = r;
}
if (r < 0 || g < 0 || b < 0 || r > 255 || g > 255 || b > 255) {
return;
}
FromDSE(dse, NULL, NULL, &d);
bgcolor[d][0] = r;
bgcolor[d][1] = g;
bgcolor[d][2] = b;
}
static void
SetMoonEntry(int dse, char const *moon)
{
int phase;
int d;
char msg[28];
/* Don't bother unless it's utf-8 */
if (!encoding_is_utf8) {
return;
}
msg[0] = 0;
if (sscanf(moon, "%d %*d %*d %27[^\x01]", &phase, msg) < 4) {
if (sscanf(moon, "%d", &phase) != 1) {
/* Malformed MOON special; ignore */
return;
}
}
if (phase < 0 || phase > 3) {
/* Bad phase */
return;
}
FromDSE(dse, NULL, NULL, &d);
if (msg[0]) {
snprintf(moons[d], sizeof(moons[d]), "%s %s", moonphase_emojis[phase], msg);
} else {
snprintf(moons[d], sizeof(moons[d]), "%s", moonphase_emojis[phase]);
}
}
/***************************************************************/
/* */
/* ProduceCalendar */
/* */
/* Main loop for generating a calendar. */
/* */
/***************************************************************/
void ProduceCalendar(void)
{
int y, m, d;
/* Check if current locale is UTF-8, if we have langinfo.h */
#ifdef HAVE_LANGINFO_H
char const *encoding = nl_langinfo(CODESET);
if (!strcasecmp(encoding, "utf-8")) {
encoding_is_utf8 = 1;
}
#endif
if (UseUTF8Chars) {
linestruct = &UTF8Drawing;
} else if (UseVTChars) {
linestruct = &VT100Drawing;
} else {
linestruct = &NormalDrawing;
}
ShouldCache = 1;
ColSpaces = (CalWidth - 9) / 7;
CalWidth = 7*ColSpaces + 8;
/* Run the file once to get potentially-overridden day names */
if (CalMonths) {
FromDSE(DSEToday, &y, &m, &d);
DSEToday = DSE(y, m, 1);
LocalDSEToday = DSEToday;
LocalSysTime = 0;
SysTime = 0;
GenerateCalEntries(-1);
DidAMonth = 0;
if (PsCal == PSCAL_LEVEL3) {
printf("[\n");
}
while (CalMonths--) {
DoCalendarOneMonth();
DidAMonth = 1;
}
if (PsCal == PSCAL_LEVEL3) {
printf("\n]\n");
}
return;
} else {
if (MondayFirst) {
DSEToday -= (DSEToday%7);
LocalDSEToday -= (LocalDSEToday%7);
} else {
DSEToday -= ((DSEToday+1)%7);
LocalDSEToday -= ((LocalDSEToday+1)%7);
}
LocalSysTime = 0;
SysTime = 0;
GenerateCalEntries(-1);
if (!DoSimpleCalendar) {
WriteWeekHeaderLine();
WriteCalDays();
WriteIntermediateCalLine();
}
DidAWeek = 0;
if (PsCal == PSCAL_LEVEL3) {
printf("[\n");
}
while (CalWeeks--) {
DoCalendarOneWeek(CalWeeks);
DidAWeek = 1;
}
if (PsCal == PSCAL_LEVEL3) {
printf("\n]\n");
}
return;
}
}
static void
SendTranslationTable(int pslevel)
{
if (pslevel < PSCAL_LEVEL3) {
printf("# translations\n");
}
DumpTranslationTable(stdout, 1);
if (pslevel < PSCAL_LEVEL3) {
printf("\n");
}
}
/***************************************************************/
/* */
/* DoCalendarOneWeek */
/* */
/* Write a calendar for a single week */
/* */
/***************************************************************/
static void DoCalendarOneWeek(int nleft)
{
int y, m, d, done, i, l, wd;
char buf[128];
int LinesWritten = 0;
int OrigDse = LocalDSEToday;
InitMoonsAndShades();
/* Fill in the column entries */
for (i=0; i<7; i++) {
ColToDay[i] = DayOf(DSEToday);
GenerateCalEntries(i);
DSEToday++;
LocalDSEToday++;
}
/* Figure out weekday of first column */
if (MondayFirst) wd = DSEToday % 7;
else wd = (DSEToday + 1) % 7;
/* Output the entries */
/* If it's "Simple Calendar" format, do it simply... */
if (DoSimpleCalendar) {
if (PsCal == PSCAL_LEVEL3) {
if (DidAWeek) {
printf(",\n");
}
printf("{\n\"caltype\":\"weekly\",");
if (!DidAWeek) {
printf("\"translations\":");
SendTranslationTable(PsCal);
printf(",");
}
printf("\"dates\":[");
for (i=0; i<7; i++) {
if (i != 0) {
printf(",");
}
FromDSE(OrigDse+i-wd, &y, &m, &d);
printf("{\"dayname\":\"%s\",\"date\":\"%04d-%02d-%02d\",\"year\":%d,\"month\":\"%s\",\"day\":%d}", get_day_name((OrigDse+i-wd)%7),y, m+1, d, y, get_month_name(m), d);
}
printf("],\"entries\":[");
}
DidADay = 0;
for (i=0; i<7; i++) {
WriteSimpleEntries(i, OrigDse+i-wd);
}
if (PsCal == PSCAL_LEVEL3) {
printf("\n]\n}");
}
return;
}
/* Here come the first few lines... */
gon();
DRAW(tb);
goff();
for (i=0; i<7; i++) {
FromDSE(OrigDse+i, &y, &m, &d);
char const *mon = get_month_name(m);
if (moons[d][0]) {
if (weeks[d][0]) {
snprintf(buf, sizeof(buf), "%d %s %s %s ", d, get_month_abbrev(mon), weeks[d], moons[d]);
} else {
snprintf(buf, sizeof(buf), "%d %s %s ", d, get_month_abbrev(mon), moons[d]);
}
} else {
if (weeks[d][0]) {
snprintf(buf, sizeof(buf), "%d %s %s ", d, get_month_abbrev(mon), weeks[d]);
} else {
snprintf(buf, sizeof(buf), "%d %s ", d, get_month_abbrev(mon));
}
}
if (OrigDse+i == RealToday) {
if (UseVTColors) {
printf("\x1B[1m"); /* Bold */
}
Backgroundize(d);
PrintLeft(buf, ColSpaces-1, '*');
putchar(' ');
UnBackgroundize(d);
if (UseVTColors) {
printf("\x1B[0m"); /* Normal */
}
} else {
Backgroundize(d);
PrintLeft(buf, ColSpaces, ' ');
UnBackgroundize(d);
}
gon();
DRAW(tb);
goff();
}
putchar('\n');
for (l=0; l<CalPad; l++) {
gon();
DRAW(tb);
goff();
for (i=0; i<7; i++) {
d = ColToDay[i];
Backgroundize(d);
PrintLeft("", ColSpaces, ' ');
UnBackgroundize(d);
gon();
DRAW(tb);
goff();
}
putchar('\n');
}
/* Write the body lines */
done = 0;
while (!done) {
done = WriteOneCalLine(OrigDse, wd);
if (done < 0) {
break;
}
LinesWritten++;
}
/* Write any blank lines required */
while (LinesWritten++ < CalLines) {
gon();
DRAW(tb);
goff();
for (i=0; i<7; i++) {
d = ColToDay[i];
Backgroundize(d);
PrintLeft("", ColSpaces, ' ');
UnBackgroundize(d);
gon();
DRAW(tb);
goff();
}
putchar('\n');
}
/* Write the final line */
if (nleft) {
WriteIntermediateCalLine();
} else {
WriteBottomCalLine();
}
}
/***************************************************************/
/* */
/* DoSimpleCalendarOneMonth */
/* */
/* Produce a "simple" calendar for the current month. */
/* */
/* A simple calendar is produced if the -s or -p option */
/* was used. */
/* */
/***************************************************************/
static void DoSimpleCalendarOneMonth(void)
{
int y, m, d, mm, yy, i, j;
InitMoonsAndShades();
DidADay = 0;
if (PsCal) {
FromDSE(DSEToday, &y, &m, &d);
if (PsCal == PSCAL_LEVEL1) {
if (!DidAMonth) {
SendTranslationTable(PsCal);
}
printf("%s\n", PSBEGIN);
} else if (PsCal == PSCAL_LEVEL2) {
if (!DidAMonth) {
SendTranslationTable(PsCal);
}
printf("%s\n", PSBEGIN2);
} else {
if (DidAMonth) {
printf(",\n");
}
printf("{\n");
if (!DidAMonth) {
printf("\"translations\":");
SendTranslationTable(PsCal);
printf(",");
}
}
if (PsCal < PSCAL_LEVEL3) {
printf("%s %d %d %d %d\n",
despace(get_month_name(m)), y, DaysInMonth(m, y), (DSEToday+1) % 7,
MondayFirst);
for (i=0; i<7; i++) {
j=(i+6)%7;
if (i) {
printf(" %s", despace(get_day_name(j)));
} else {
printf("%s", despace(get_day_name(j)));
}
}
printf("\n");
} else {
PrintJSONKeyPairString("caltype", "monthly");
PrintJSONKeyPairString("monthname", get_month_name(m));
PrintJSONKeyPairInt("year", y);
PrintJSONKeyPairInt("daysinmonth", DaysInMonth(m, y));
PrintJSONKeyPairInt("firstwkday", (DSEToday+1) % 7);
PrintJSONKeyPairInt("mondayfirst", MondayFirst);
printf("\"daynames\":[\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\"],",
get_day_name(6), get_day_name(0), get_day_name(1), get_day_name(2),
get_day_name(3), get_day_name(4), get_day_name(5));
}
mm = m-1;
if (mm<0) {
mm = 11; yy = y-1;
} else yy=y;
if (PsCal < PSCAL_LEVEL3) {
printf("%s %d\n", despace(get_month_name(mm)), DaysInMonth(mm,yy));
} else {
PrintJSONKeyPairString("prevmonthname", get_month_name(mm));
PrintJSONKeyPairInt("daysinprevmonth", DaysInMonth(mm, yy));
PrintJSONKeyPairInt("prevmonthyear", yy);
}
mm = m+1;
if (mm>11) {
mm = 0; yy = y+1;
} else yy=y;
if (PsCal < PSCAL_LEVEL3) {
printf("%s %d\n", despace(get_month_name(mm)), DaysInMonth(mm,yy));
} else {
PrintJSONKeyPairString("nextmonthname", get_month_name(mm));
PrintJSONKeyPairInt("daysinnextmonth", DaysInMonth(mm, yy));
PrintJSONKeyPairInt("nextmonthyear", yy);
printf("\"entries\":[\n");
}
}
while (WriteCalendarRow()) /* continue */;
if (PsCal == PSCAL_LEVEL1) {
printf("%s\n", PSEND);
} else if (PsCal == PSCAL_LEVEL2) {
printf("%s\n", PSEND2);
} else if (PsCal == PSCAL_LEVEL3){
if (DidADay) {
printf("\n");
}
printf("]\n}");
}
}
/***************************************************************/
/* */
/* DoCalendarOneMonth */
/* */
/* Produce a calendar for the current month. */
/* */
/***************************************************************/
static void DoCalendarOneMonth(void)
{
InitMoonsAndShades();
if (DoSimpleCalendar) {
DoSimpleCalendarOneMonth();
return;
}
WriteCalHeader();
while (WriteCalendarRow()) /* continue */;
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 OrigDse = DSEToday;
int LinesWritten = 0;
int moreleft;
/* Get the date of the first day */
FromDSE(DSEToday, &y, &m, &d);
if (!MondayFirst) wd = (DSEToday + 1) % 7;
else wd = DSEToday % 7;
for (i=0; i<7; i++) {
ColToDay[i] = 0;
}
/* Fill in the column entries */
for (i=wd; i<7; i++) {
if (d+i-wd > DaysInMonth(m, y)) break;
GenerateCalEntries(i);
ColToDay[i] = DayOf(DSEToday);
DSEToday++;
LocalDSEToday++;
}
/* 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, OrigDse+i-wd);
}
return (d+7-wd <= DaysInMonth(m, y));
}
/* Here come the first few lines... */
gon();
DRAW(tb);
goff();
for (i=0; i<7; i++) {
if (i < wd || d+i-wd>DaysInMonth(m, y))
PrintLeft("", ColSpaces, ' ');
else {
if (moons[d+i-wd][0]) {
if (weeks[d+i-wd][0]) {
snprintf(buf, sizeof(buf), "%d %s %s ", d+i-wd, weeks[d+i-wd], moons[d+i-wd]);
} else {
snprintf(buf, sizeof(buf), "%d %s ", d+i-wd, moons[d+i-wd]);
}
} else {
if (weeks[d+i-wd][0]) {
snprintf(buf, sizeof(buf), "%d %s ", d+i-wd, weeks[d+i-wd]);
} else {
snprintf(buf, sizeof(buf), "%d ", d+i-wd);
}
}
if (DSE(y, m, d+i-wd) == RealToday) {
if (UseVTColors) {
printf("\x1B[1m"); /* Bold */
}
Backgroundize(d+i-wd);
PrintLeft(buf, ColSpaces-1, '*');
putchar(' ');
if (UseVTColors) {
printf("\x1B[0m"); /* Normal */
}
UnBackgroundize(d+i-wd);
} else {
Backgroundize(d+i-wd);
PrintLeft(buf, ColSpaces, ' ');
UnBackgroundize(d+i-wd);
}
}
gon();
DRAW(tb);
goff();
}
putchar('\n');
for (l=0; l<CalPad; l++) {
gon();
DRAW(tb);
goff();
for (i=0; i<7; i++) {
Backgroundize(ColToDay[i]);
PrintLeft("", ColSpaces, ' ');
UnBackgroundize(ColToDay[i]);
gon();
DRAW(tb);
goff();
}
putchar('\n');
}
/* Write the body lines */
done = 0;
while (!done) {
done = WriteOneCalLine(OrigDse, wd);
if (done < 0) {
break;
}
LinesWritten++;
}
/* Write any blank lines required */
while (LinesWritten++ < CalLines) {
gon();
DRAW(tb);
goff();
for (i=0; i<7; i++) {
Backgroundize(ColToDay[i]);
PrintLeft("", ColSpaces, ' ');
UnBackgroundize(ColToDay[i]);
gon();
DRAW(tb);
goff();
}
putchar('\n');
}
moreleft = (d+7-wd <= DaysInMonth(m, y));
if (moreleft) {
WriteIntermediateCalLine();
} else {
WriteBottomCalLine();
}
/* Return non-zero if we have not yet finished */
return moreleft;
}
/***************************************************************/
/* */
/* PrintLeft */
/* */
/* Left-justify a piece of text. */
/* */
/***************************************************************/
static void PrintLeft(char const *s, int width, char pad)
{
size_t len = mbstowcs(NULL, s, 0);
int i;
wchar_t static_buf[128];
wchar_t *buf;
wchar_t *ws;
if (!len) {
for (i=0; i<width; i++) {
fputc(pad, stdout);
}
return;
}
if (len + 1 <= 128) {
buf = static_buf;
} else {
buf = calloc(len+1, sizeof(wchar_t));
if (!buf) {
/* Uh-oh... cannot recover */
fprintf(ErrFp, "%s\n", GetErr(E_NO_MEM));
exit(EXIT_FAILURE);
}
}
(void) mbstowcs(buf, s, len+1);
ws = buf;
i=0;
while (i<width) {
if (*ws) {
if (i + wcwidth(*ws) > width) {
break;
}
i += wcwidth(*ws);
PutWideChar(*ws++, NULL);
} else {
break;
}
}
/* Mop up any potential combining characters */
while (*ws && wcwidth(*ws) == 0) {
PutWideChar(*ws++, NULL);
}
/* Possibly send lrm control sequence */
send_lrm();
while (i<width) {
fputc(pad, stdout);
i++;
}
if (buf != static_buf) free(buf);
}
/***************************************************************/
/* */
/* PrintCentered */
/* */
/* Center a piece of text */
/* */
/***************************************************************/
static void PrintCentered(char const *s, int width, char const *pad)
{
size_t len = mbstowcs(NULL, s, 0);
int display_len;
int i;
wchar_t static_buf[128];
wchar_t *buf;
wchar_t *ws;
int d;
if (!len) {
for (i=0; i<width; i++) {
fputs(pad, stdout);
}
return;
}
if (len + 1 <= 128) {
buf = static_buf;
} else {
buf = calloc(len+1, sizeof(wchar_t));
if (!buf) {
/* Uh-oh... cannot recover */
fprintf(ErrFp, "%s\n", GetErr(E_NO_MEM));
exit(EXIT_FAILURE);
}
}
(void) mbstowcs(buf, s, len+1);
display_len = wcswidth(buf, len+1);
d = (width - display_len) / 2;
if (d < 0) d = 0;
ws = buf;
for (i=0; i<d; i++) fputs(pad, stdout);
i=0;
while (i+d < width) {
if (*ws) {
if (i+d + wcwidth(*ws) > width) {
break;
}
i += wcwidth(*ws);
PutWideChar(*ws++, NULL);
} else {
break;
}
}
/* Mop up any potential combining characters */
while (*ws && wcwidth(*ws) == 0) {
PutWideChar(*ws++, NULL);
}
/* Possibly send lrm control sequence */
send_lrm();
while (i+d<width) {
fputs(pad, stdout);
i++;
}
if (buf != static_buf) free(buf);
}
/***************************************************************/
/* */
/* WriteOneCalLine */
/* */
/* Write a single line. */
/* Returns: -1 if no line was written and nothing left to do */
/* 0 if a line was written and there is more to do */
/* 1 if a line was written and nothing left to do */
/* */
/***************************************************************/
static int WriteOneCalLine(int start_dse, int wd)
{
int done = 1, i;
int d;
/* Do nothing if there are no calendar entries at all */
d = 0;
for (i=0; i<7; i++) {
if (CalColumn[i]) {
d = 1;
break;
}
}
if (!d) {
/* Nothing to do and no line was written */
return -1;
}
gon();
DRAW(tb);
goff();
for (i=0; i<7; i++) {
FromDSE(start_dse+i, NULL, NULL, &d);
d -= wd;
if (CalColumn[i]) {
Backgroundize(ColToDay[i]);
if (WriteOneColLine(i)) done = 0;
} else {
Backgroundize(ColToDay[i]);
PrintCentered("", ColSpaces, " ");
}
UnBackgroundize(ColToDay[i]);
gon();
DRAW(tb);
goff();
}
putchar('\n');
return done;
}
static void FreeCalEntry(CalEntry *e)
{
if (e->text) free(e->text);
if (e->raw_text) free(e->raw_text);
FreeTrigInfoChain(e->infos);
if (e->wc_text) free(e->wc_text);
}
/***************************************************************/
/* */
/* WriteOneColLine */
/* */
/* Write a single line for a specified column. Return 1 if */
/* the column still has entries; 0 otherwise. */
/* */
/***************************************************************/
static int WriteOneColLine(int col)
{
CalEntry *e = CalColumn[col];
char const *s;
char const *space;
wchar_t const *ws;
wchar_t const *wspace;
int width;
int clamp = 1;
int numwritten = 0;
int d = ColToDay[col];
char const *url;
if (d && UseBGVTColors && bgcolor[d][0] != -1) {
clamp = 0;
}
PRINTROW:
url = get_url(e->infos);
/* Print as many characters as possible within the column */
if (e->wc_text) {
wspace = NULL;
ws = e->wc_pos;
/* If we're at the end, and there's another entry, do a blank
line and move to next entry. */
if (!*ws && e->next) {
if (CalSepLine) {
PrintLeft("", ColSpaces, ' ');
}
CalColumn[col] = e->next;
FreeCalEntry(e);
if (!CalSepLine) {
e = CalColumn[col];
goto PRINTROW;
} else {
return 1;
}
}
/* Find the last space char within the column. */
width = 0;
while (width <= ColSpaces) {
if (!*ws || *ws == '\n') {
wspace = ws;
break;
}
if (iswspace(*ws)) {
wspace = ws;
width++;
} else {
if (wcwidth(*ws)) {
width += wcwidth(*ws);
if (width > ColSpaces) {
break;
}
}
}
ws++;
}
/* Colorize reminder if necessary */
if (UseVTColors && e->is_color) {
ColorizeEntry(e, clamp);
}
if (url) {
printf("\x1B]8;;%s\x1B\\", url);
}
/* If we couldn't find a space char, print what we have. */
if (!wspace) {
for (ws = e->wc_pos; numwritten < ColSpaces; ws++) {
if (!*ws) break;
if (iswspace(*ws)) {
putchar(' ');
numwritten++;
} else {
if (wcwidth(*ws) > 0) {
if (numwritten + wcwidth(*ws) > ColSpaces) {
break;
}
numwritten += wcwidth(*ws);
}
PutWideChar(*ws, NULL);
}
}
e->wc_pos = ws;
} else {
/* We found a space - print everything before it. */
for (ws = e->wc_pos; ws<wspace; ws++) {
if (!*ws) break;
if (iswspace(*ws)) {
putchar(' ');
numwritten++;
} else {
if (wcwidth(*ws) > 0) {
numwritten += wcwidth(*ws);
}
PutWideChar(*ws, NULL);
}
}
}
if (url) {
printf("\x1B]8;;\x1B\\");
}
/* Decolorize reminder if necessary, but keep any SHADE */
if (UseVTColors && e->is_color) {
printf("%s", Decolorize());
Backgroundize(d);
}
/* Possibly send lrm control sequence */
send_lrm();
/* Flesh out the rest of the column */
while(numwritten++ < ColSpaces) putchar(' ');
/* Skip any spaces before next word */
while (iswspace(*ws)) ws++;
/* If done, free memory if no next entry. */
if (!*ws && !e->next) {
CalColumn[col] = e->next;
FreeCalEntry(e);
} else {
e->wc_pos = ws;
}
if (CalColumn[col]) return 1; else return 0;
} else {
space = NULL;
s = e->pos;
/* If we're at the end, and there's another entry, do a blank
line and move to next entry. */
if (!*s && e->next) {
if (CalSepLine) {
PrintLeft("", ColSpaces, ' ');
}
CalColumn[col] = e->next;
FreeCalEntry(e);
if (!CalSepLine) {
e = CalColumn[col];
fprintf(stderr, "BLOOP\n");
goto PRINTROW;
} else {
return 1;
}
}
/* Find the last space char within the column. */
while (s - e->pos <= ColSpaces) {
if (!*s || *s == '\n') {space = s; break;}
if (isspace(*s)) space = s;
s++;
}
/* Colorize reminder if necessary */
if (UseVTColors && e->is_color) {
ColorizeEntry(e, clamp);
}
/* 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++;
if (isspace(*s)) {
putchar(' ');
} else {
putchar(*s);
}
}
e->pos = s;
} else {
/* We found a space - print everything before it. */
for (s = e->pos; s<space; s++) {
if (!*s) break;
numwritten++;
if (isspace(*s)) {
putchar(' ');
} else {
putchar(*s);
}
}
}
/* Decolorize reminder if necessary, but keep SHADE */
if (UseVTColors && e->is_color) {
printf("%s", Decolorize());
Backgroundize(d);
}
/* Flesh out the rest of the column */
while(numwritten++ < ColSpaces) putchar(' ');
/* Skip any spaces before next word */
while (isspace(*s)) s++;
/* If done, free memory if no next entry. */
if (!*s && !e->next) {
CalColumn[col] = e->next;
FreeCalEntry(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 const *s;
Parser p;
/* Do some initialization first... */
PerIterationInit();
r=IncludeFile(InitialFile);
if (r) {
fprintf(ErrFp, "%s %s: %s\n", GetErr(E_ERR_READING), InitialFile, GetErr(r));
exit(EXIT_FAILURE);
}
while(1) {
r = ReadLine();
if (r == E_EOF) return;
if (r) {
Eprint("%s: %s", GetErr(E_ERR_READING), GetErr(r));
exit(EXIT_FAILURE);
}
s = FindInitialToken(&tok, CurLine);
/* Should we ignore it? */
if (tok.type != T_If &&
tok.type != T_Else &&
tok.type != T_EndIf &&
tok.type != T_IfTrig &&
tok.type != T_Set &&
tok.type != T_Fset &&
should_ignore_line())
{
/* 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_Return: r=DoReturn(&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_IncludeSys:
case T_IncludeR: r=DoInclude(&p, tok.type); break;
case T_IncludeCmd: r=DoIncludeCmd(&p); break;
case T_Exit: DoExit(&p); break;
case T_Set: r=DoSet(&p); break;
case T_Fset: r=DoFset(&p); break;
case T_Funset: r=DoFunset(&p); break;
case T_Frename: r=DoFrename(&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_PushVars:
r=PushVars(&p);
break;
case T_PopVars:
r=PopVars(&p);
break;
case T_PushFuncs:
r=PushUserFuncs(&p);
break;
case T_PopFuncs:
r=PopUserFuncs(&p);
break;
case T_Preserve: r=DoPreserve(&p); break;
case T_Expr: r = DoExpr(&p); break;
case T_Translate: r = DoTranslate(&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:
if (!SuppressImplicitRemWarnings) {
if (warning_level("05.00.03")) {
Wprint(tr("Unrecognized command; interpreting as REM"));
}
WarnedAboutImplicit = 1;
}
CreateParser(CurLine, &p);
r=DoCalRem(&p, col);
break;
}
if (r && (!Hush || r != E_RUN_DISABLED)) Eprint("%s", GetErr(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;
FromDSE(DSEToday, &y, &m, &d);
snprintf(buf, sizeof(buf), "%s %d", get_month_name(m), y);
WriteTopCalLine();
gon();
DRAW(tb);
goff();
PrintCentered(buf, CalWidth-2, " ");
gon();
DRAW(tb);
goff();
putchar('\n');
WritePostHeaderLine();
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)
{
size_t oldLen;
Trigger trig;
TimeTrig tim;
Value v;
int r, err;
int dse;
CalEntry *CurCol;
CalEntry *e;
char const *s, *s2;
DynamicBuffer buf, obuf, pre_buf, raw_buf;
Token tok;
int nonconst_expr = 0;
int is_color, col_r, col_g, col_b;
if (col >= 0) {
CurCol = CalColumn[col];
} else {
CurCol = NULL;
}
is_color = 0;
DBufInit(&buf);
DBufInit(&pre_buf);
DBufInit(&raw_buf);
/* Parse the trigger date and time */
if ( (r=ParseRem(p, &trig, &tim)) ) {
FreeTrig(&trig);
return r;
}
if (trig.tz != NULL && tim.ttime == NO_TIME) {
FreeTrig(&trig);
return E_TZ_NO_AT;
}
/* An empty string for time zone is just a missing time zone */
if (trig.tz != NULL && !*trig.tz) {
free( (void *) trig.tz);
trig.tz = NULL;
}
if (trig.typ == MSG_TYPE ||
trig.typ == CAL_TYPE ||
trig.typ == MSF_TYPE) {
is_color = (
DefaultColorR != -1
&& DefaultColorG != -1
&& DefaultColorB != -1);
if (is_color) {
col_r = DefaultColorR;
col_g = DefaultColorG;
col_b = DefaultColorB;
}
}
if (trig.typ == NO_TYPE) {
FreeTrig(&trig);
return E_EOLN;
}
if (trig.typ == SAT_TYPE) {
EnterTimezone(trig.tz);
r=DoSatRemind(&trig, &tim, p);
ExitTimezone(trig.tz);
if (r) {
if (r == E_CANT_TRIG && trig.maybe_uncomputable) {
r = OK;
}
FreeTrig(&trig);
if (r == E_EXPIRED) return OK;
return r;
}
if (!LastTrigValid) {
FreeTrig(&trig);
return OK;
}
r=ParseToken(p, &buf);
if (r) {
FreeTrig(&trig);
return r;
}
FindToken(DBufValue(&buf), &tok);
DBufFree(&buf);
if (tok.type == T_Empty || tok.type == T_Comment) {
r = OK;
if (trig.addomit) {
r = AddGlobalOmit(LastTriggerDate);
}
FreeTrig(&trig);
return r;
}
if (tok.type != T_RemType || tok.val == SAT_TYPE) {
FreeTrig(&trig);
return E_PARSE_ERR;
}
if (tok.val == PASSTHRU_TYPE) {
r=ParseToken(p, &buf);
if (r) return r;
if (!DBufLen(&buf)) {
DBufFree(&buf);
FreeTrig(&trig);
return E_EOLN;
}
StrnCpy(trig.passthru, DBufValue(&buf), PASSTHRU_LEN);
DBufFree(&buf);
}
trig.typ = tok.val;
/* Convert some SPECIALs back to plain types */
FixSpecialType(&trig);
if (trig.typ == MSG_TYPE ||
trig.typ == CAL_TYPE ||
trig.typ == MSF_TYPE) {
is_color = (
DefaultColorR != -1
&& DefaultColorG != -1
&& DefaultColorB != -1);
if (is_color) {
col_r = DefaultColorR;
col_g = DefaultColorG;
col_b = DefaultColorB;
}
}
dse = LastTriggerDate;
if (!LastTrigValid) {
FreeTrig(&trig);
return OK;
}
if (dse < 0 && !ParseUntriggered) {
/* Expired */
FreeTrig(&trig);
return OK;
}
} else {
/* Calculate the trigger date */
EnterTimezone(trig.tz);
dse = ComputeTrigger(get_scanfrom(&trig), &trig, &tim, &r, 1);
ExitTimezone(trig.tz);
if (r) {
if (r == E_CANT_TRIG && trig.maybe_uncomputable) {
r = OK;
}
FreeTrig(&trig);
return r;
}
if (dse < 0 && !ParseUntriggered) {
/* Expired */
FreeTrig(&trig);
return OK;
}
}
/* Adjust trigger date/time to time zone */
dse = AdjustTriggerForTimeZone(&trig, dse, &tim, 1);
/* Add to global OMITs if so indicated */
if (trig.addomit) {
r = AddGlobalOmit(dse);
if (r) {
FreeTrig(&trig);
return r;
}
}
/* If we're not actually generating any calendar entries, we're done */
if (col < 0) {
FreeTrig(&trig);
return OK;
}
/* Don't include timed reminders in calendar if -a option supplied. */
if (DontIssueAts && tim.ttime != NO_TIME) {
FreeTrig(&trig);
return OK;
}
/* Filter unwanted events/todos */
if ((TodoFilter == ONLY_TODOS && !trig.is_todo) ||
(TodoFilter == ONLY_EVENTS && trig.is_todo)) {
FreeTrig(&trig);
return OK;
}
/* Save nonconst_expr flag */
nonconst_expr = trig.nonconst_expr;
/* 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 it's a plain reminder but we have a default color, add the
three colors to the prebuf and change passthru to "COLOR" */
if (trig.typ == MSG_TYPE ||
trig.typ == CAL_TYPE ||
trig.typ == MSF_TYPE) {
if ((PsCal || DoSimpleCalendar) && is_color) {
char cbuf[24];
snprintf(cbuf, sizeof(cbuf), "%d %d %d ", col_r, col_g, col_b);
DBufPuts(&pre_buf, cbuf);
strcpy(trig.passthru, "COLOR");
/* Don't change trig.typ or next if() will trigger! */
}
}
if (trig.typ == PASSTHRU_TYPE) {
if (!PsCal && !strcasecmp(trig.passthru, "SHADE")) {
if (dse == DSEToday) {
DBufInit(&obuf);
r = DoSubst(p, &obuf, &trig, &tim, dse, CAL_MODE);
if (r) {
DBufFree(&obuf);
FreeTrig(&trig);
return r;
}
SetShadeEntry(dse, DBufValue(&obuf));
DBufFree(&obuf);
}
}
if (!PsCal && !strcasecmp(trig.passthru, "WEEK")) {
if (dse == DSEToday) {
DBufInit(&obuf);
r = DoSubst(p, &obuf, &trig, &tim, dse, CAL_MODE);
if (r) {
DBufFree(&obuf);
FreeTrig(&trig);
return r;
}
sscanf(DBufValue(&obuf), "%31[^\x01]", weeks[DayOf(dse)]);
DBufFree(&obuf);
}
}
if (!PsCal && strcasecmp(trig.passthru, "COLOR") && strcasecmp(trig.passthru, "COLOUR") && strcasecmp(trig.passthru, "MOON")) {
FreeTrig(&trig);
return OK;
}
if (!PsCal && !strcasecmp(trig.passthru, "MOON")) {
if (dse == DSEToday) {
DBufInit(&obuf);
r = DoSubst(p, &obuf, &trig, &tim, dse, CAL_MODE);
if (r) {
DBufFree(&obuf);
FreeTrig(&trig);
return r;
}
SetMoonEntry(dse, DBufValue(&obuf));
DBufFree(&obuf);
}
}
if (!strcasecmp(trig.passthru, "COLOR") ||
!strcasecmp(trig.passthru, "COLOUR")) {
is_color = 1;
/* Strip off the three color numbers */
DBufFree(&buf);
r=ParseToken(p, &buf);
DBufPuts(&pre_buf, DBufValue(&buf));
DBufPutc(&pre_buf, ' ');
DBufFree(&buf);
if (r) {
FreeTrig(&trig);
return r;
}
r=ParseToken(p, &buf);
DBufPuts(&pre_buf, DBufValue(&buf));
DBufPutc(&pre_buf, ' ');
DBufFree(&buf);
if (r) {
FreeTrig(&trig);
return r;
}
r=ParseToken(p, &buf);
DBufPuts(&pre_buf, DBufValue(&buf));
DBufPutc(&pre_buf, ' ');
DBufFree(&buf);
if (r) {
FreeTrig(&trig);
return r;
}
(void) sscanf(DBufValue(&pre_buf), "%d %d %d",
&col_r, &col_g, &col_b);
if (col_r < 0 || col_g < 0 || col_b < 0 ||
col_r > 255 || col_g > 255 || col_b > 255) {
is_color = 0;
col_r = -1;
col_g = -1;
col_b = -1;
trig.passthru[0] = 0;
DBufFree(&pre_buf);
} else {
if (!PsCal && !DoSimpleCalendar) {
DBufFree(&pre_buf);
}
}
}
}
/* If trigger date == today, add it to the current entry */
DBufInit(&obuf);
if ((dse == DSEToday) ||
(DoSimpleCalDelta &&
ShouldTriggerReminder(&trig, &tim, dse, &err))) {
/* The parse_ptr should not be nested, but just in case... */
if (!p->isnested) {
if (DBufPuts(&raw_buf, p->pos) != OK) {
DBufFree(&obuf);
DBufFree(&pre_buf);
FreeTrig(&trig);
return E_NO_MEM;
}
}
if (DoSimpleCalendar || tim.ttime != NO_TIME) {
/* Suppress time if it's not today or if it's a non-COLOR special */
if (dse != DSEToday ||
(trig.typ == PASSTHRU_TYPE &&
strcasecmp(trig.passthru, "COLOUR") &&
strcasecmp(trig.passthru, "COLOR"))) {
if (DBufPuts(&obuf, SimpleTime(NO_TIME)) != OK) {
DBufFree(&obuf);
DBufFree(&raw_buf);
DBufFree(&pre_buf);
FreeTrig(&trig);
return E_NO_MEM;
}
} else {
if (DBufPuts(&obuf, CalendarTime(tim.ttime, tim.duration)) != OK) {
DBufFree(&raw_buf);
DBufFree(&obuf);
DBufFree(&pre_buf);
FreeTrig(&trig);
return E_NO_MEM;
}
}
}
if (trig.typ != PASSTHRU_TYPE &&
UserFuncExists("calprefix")==1) {
char evalBuf[64];
snprintf(evalBuf, sizeof(evalBuf), "calprefix(%d)", trig.priority);
s2 = evalBuf;
r = EvalExpr(&s2, &v, NULL);
if (!r) {
if (!DoCoerce(STR_TYPE, &v)) {
if (DBufPuts(&obuf, v.v.str) != OK) {
DestroyValue(v);
DBufFree(&raw_buf);
DBufFree(&obuf);
DBufFree(&pre_buf);
FreeTrig(&trig);
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 (dse != DSEToday) {
r = DoSubst(p, &obuf, &trig, &tim, dse, ADVANCE_MODE);
} else {
r = DoSubst(p, &obuf, &trig, &tim, dse, CAL_MODE);
}
if (r) {
DBufFree(&pre_buf);
DBufFree(&obuf);
DBufFree(&raw_buf);
FreeTrig(&trig);
return r;
}
if (DBufLen(&obuf) <= oldLen) {
DBufFree(&obuf);
DBufFree(&pre_buf);
DBufFree(&raw_buf);
FreeTrig(&trig);
return OK;
}
if (trig.typ != PASSTHRU_TYPE &&
UserFuncExists("calsuffix")==1) {
char evalBuf[64];
snprintf(evalBuf, sizeof(evalBuf), "calsuffix(%d)", trig.priority);
s2 = evalBuf;
r = EvalExpr(&s2, &v, NULL);
if (!r) {
if (!DoCoerce(STR_TYPE, &v)) {
if (DBufPuts(&obuf, v.v.str) != OK) {
DestroyValue(v);
DBufFree(&raw_buf);
DBufFree(&obuf);
DBufFree(&pre_buf);
FreeTrig(&trig);
return E_NO_MEM;
}
}
DestroyValue(v);
}
}
s = DBufValue(&obuf);
if (DedupeReminders) {
if (ShouldDedupe(dse, tim.ttime, DBufValue(&obuf))) {
DBufFree(&obuf);
DBufFree(&raw_buf);
DBufFree(&pre_buf);
FreeTrig(&trig);
return OK;
}
}
if (!DoSimpleCalendar) while (isempty(*s)) s++;
DBufPuts(&pre_buf, s);
s = DBufValue(&pre_buf);
NumTriggered++;
e = NEW(CalEntry);
if (!e) {
DBufFree(&obuf);
DBufFree(&raw_buf);
DBufFree(&pre_buf);
FreeTrig(&trig);
return E_NO_MEM;
}
e->infos = NULL;
e->nonconst_expr = nonconst_expr;
e->if_depth = get_if_pointer() - get_base_if_pointer();
e->trig = trig;
if (e->trig.tz) {
e->trig.tz = strdup(e->trig.tz);
}
e->tt = tim;
e->wc_pos = NULL;
e->wc_text = NULL;
e->is_color = is_color;
e->r = col_r;
e->g = col_g;
e->b = col_b;
e->text = strdup(s);
e->raw_text = strdup(DBufValue(&raw_buf));
DBufFree(&raw_buf);
DBufFree(&obuf);
DBufFree(&pre_buf);
if (!e->text || !e->raw_text) {
if (e->text) free(e->text);
if (e->raw_text) free(e->raw_text);
FreeTrigInfoChain(e->infos);
free(e);
FreeTrig(&trig);
return E_NO_MEM;
}
make_wchar_versions(e);
DBufInit(&(e->tags));
DBufPuts(&(e->tags), DBufValue(&(trig.tags)));
if (SynthesizeTags) {
AppendTag(&(e->tags), SynthesizeTag());
}
/* Take over any TrigInfo! */
e->infos = trig.infos;
trig.infos = NULL;
/* Don't need tags any more */
FreeTrig(&trig);
e->duration = tim.duration;
e->priority = trig.priority;
e->filename = GetCurrentFilename();
e->lineno = LineNo;
e->lineno_start = LineNoStart;
if (trig.typ == PASSTHRU_TYPE || is_color) {
StrnCpy(e->passthru, trig.passthru, PASSTHRU_LEN);
} else {
e->passthru[0] = 0;
}
e->pos = e->text;
if (dse == DSEToday) {
e->time = tim.ttime;
} else {
e->time = NO_TIME;
}
e->next = CurCol;
CalColumn[col] = e;
SortCol(&CalColumn[col]);
} else {
/* Parse the rest of the line to catch expression-pasting errors */
while (ParseChar(p, &r, 0)) {
if (r != 0) {
return r;
}
}
}
return OK;
}
static void WriteSimpleEntryProtocol1(CalEntry const *e)
{
if (e->passthru[0]) {
printf(" %s", e->passthru);
} else {
printf(" *");
}
if (*DBufValue(&(e->tags))) {
printf(" %s ", DBufValue(&(e->tags)));
} else {
printf(" * ");
}
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);
}
void WriteJSONTimeTrigger(TimeTrig const *tt)
{
PrintJSONKeyPairTime("time", tt->ttime);
if (tt->ttime != tt->ttime_orig) {
PrintJSONKeyPairTime("time_in_tz", tt->ttime_orig);
}
PrintJSONKeyPairTime("nexttime", tt->nexttime);
PrintJSONKeyPairInt("tdelta", tt->delta);
PrintJSONKeyPairInt("trep", tt->rep);
if (tt->duration != NO_TIME) {
PrintJSONKeyPairInt("duration", tt->duration);
}
}
static char const *
get_url(TrigInfo *infos)
{
TrigInfo *ti = infos;
char const *url;
if (!TerminalHyperlinks) {
/* Nope, not doing links in terminal */
return NULL;
}
while (ti) {
char const *colon = strchr(ti->info, ':');
if (!colon) {
ti = ti->next;
continue;
}
if (!strncasecmp(ti->info, "url", colon-ti->info)) {
url = colon+1;
while (*url && isspace(*url)) {
url++;
}
if (!*url) return NULL;
return url;
}
ti = ti->next;
}
return NULL;
}
void
WriteJSONInfoChain(TrigInfo *ti)
{
printf("\"info\":{");
while (ti) {
/* Skanky... */
char *colon = (char *) strchr(ti->info, ':');
char const *value;
if (!colon) {
/* Should be impossible... */
ti = ti->next;
continue;
}
/* Terminate the string at the colon */
*colon = 0;
value = colon+1;
while(*value && isspace(*value)) {
value++;
}
printf("\"");
PrintJSONStringLC(ti->info);
printf("\":\"");
PrintJSONString(value);
printf("\"");
/* Restore the value of the colon */
*colon = ':';
if (ti->next) {
printf(",");
}
ti = ti->next;
}
printf("},");
}
void WriteJSONTrigger(Trigger const *t, int include_tags)
{
/* wd is an array of days from 0=monday to 6=sunday.
We convert to array of strings */
if (t->wd != NO_WD) {
printf("\"wd\":[");
int done = 0;
int i;
for (i=0; i<7; i++) {
if (t->wd & (1 << i)) {
if (done) {
printf(",");
}
done = 1;
printf("\"%s\"", DayName[i]);
}
}
printf("],");
}
if (t->d != NO_DAY) {
PrintJSONKeyPairInt("d", t->d);
}
if (t->m != NO_MON) {
PrintJSONKeyPairInt("m", t->m+1);
}
if (t->y != NO_YR) {
PrintJSONKeyPairInt("y", t->y);
}
PrintJSONKeyPairInt("is_todo", t->is_todo);
PrintJSONKeyPairDate("complete_through", t->complete_through);
if (t->back) {
PrintJSONKeyPairInt("back", t->back);
}
if (t->delta) {
PrintJSONKeyPairInt("delta", t->delta);
}
if (t->rep) {
PrintJSONKeyPairInt("rep", t->rep);
}
if (t->d != NO_DAY && t->m != NO_MON && t->y != NO_YR) {
printf("\"trigbase\":\"%04d-%02d-%-2d\",",
t->y, t->m+1, t->d);
}
/* Local omit is an array of days from 0=monday to 6=sunday.
We convert to array of strings */
if (t->localomit != NO_WD) {
printf("\"localomit\":[");
int done = 0;
int i;
for (i=0; i<7; i++) {
if (t->localomit & (1 << i)) {
if (done) {
printf(",");
}
done = 1;
printf("\"%s\"", DayName[i]);
}
}
printf("],");
}
switch(t->skip) {
case SKIP_SKIP:
PrintJSONKeyPairString("skip", "SKIP");
break;
case BEFORE_SKIP:
PrintJSONKeyPairString("skip", "BEFORE");
break;
case AFTER_SKIP:
PrintJSONKeyPairString("skip", "AFTER");
break;
}
PrintJSONKeyPairDate("until", t->until);
if (t->once != NO_ONCE) {
PrintJSONKeyPairInt("once", t->once);
}
if (t->scanfrom != NO_SCANFROM) {
if (t->scanfrom >= 0) {
PrintJSONKeyPairDate("scanfrom", t->scanfrom);
} else {
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);
if (t->eventstart_orig != NO_TIME &&
t->eventstart_orig != t->eventstart) {
PrintJSONKeyPairDateTime("eventstart_in_tz", t->eventstart_orig);
}
if (t->eventduration != NO_TIME) {
PrintJSONKeyPairInt("eventduration", t->eventduration);
}
if (t->maybe_uncomputable) {
PrintJSONKeyPairInt("maybe_uncomputable", 1);
}
if (t->noqueue) {
PrintJSONKeyPairInt("noqueue", 1);
}
PrintJSONKeyPairString("sched", t->sched);
PrintJSONKeyPairString("warn", t->warn);
PrintJSONKeyPairString("omitfunc", t->omitfunc);
if (t->addomit) {
PrintJSONKeyPairInt("addomit", 1);
}
if (include_tags) {
if (t->infos) {
WriteJSONInfoChain(t->infos);
}
PrintJSONKeyPairString("tags", DBufValue(&(t->tags)));
}
PrintJSONKeyPairString("tz", t->tz);
}
static void WriteSimpleEntryProtocol2(CalEntry *e)
{
char const *s;
if (DoPrefixLineNo) {
PrintJSONKeyPairString("filename", e->filename);
PrintJSONKeyPairInt("lineno", e->lineno);
if (e->lineno != e->lineno_start) {
PrintJSONKeyPairInt("lineno_start", e->lineno_start);
}
}
PrintJSONKeyPairString("passthru", e->passthru);
PrintJSONKeyPairString("tags", DBufValue(&(e->tags)));
if (e->infos) {
WriteJSONInfoChain(e->infos);
}
if (e->duration != NO_TIME) {
PrintJSONKeyPairInt("duration", e->duration);
}
if (e->time != NO_TIME) {
PrintJSONKeyPairInt("time", e->time);
if (e->tt.ttime_orig != e->tt.ttime) {
PrintJSONKeyPairInt("time_in_tz", e->tt.ttime_orig);
}
if (e->tt.delta) {
PrintJSONKeyPairInt("tdelta", e->tt.delta);
}
if (e->tt.rep) {
PrintJSONKeyPairInt("trep", e->tt.rep);
}
}
WriteJSONTrigger(&e->trig, 0);
if (e->nonconst_expr) {
PrintJSONKeyPairInt("nonconst_expr", e->nonconst_expr);
}
if (e->if_depth) {
PrintJSONKeyPairInt("if_depth", e->if_depth);
}
if (e->is_color) {
PrintJSONKeyPairInt("r", e->r);
PrintJSONKeyPairInt("g", e->g);
PrintJSONKeyPairInt("b", e->b);
} else if (!strcasecmp(e->passthru, "SHADE")) {
int r, g, b, n;
n = sscanf(e->text, "%d %d %d", &r, &g, &b);
if (n < 3) {
g = r;
b = r;
}
if (r < 0) r = 0;
else if (r > 255) r = 255;
if (g < 0) g = 0;
else if (g > 255) g = 255;
if (b < 0) b = 0;
else if (b > 255) b = 255;
PrintJSONKeyPairInt("r", r);
PrintJSONKeyPairInt("g", g);
PrintJSONKeyPairInt("b", b);
}
/* Only print rawbody if it differs from body */
if (strcmp(e->raw_text, e->text)) {
PrintJSONKeyPairString("rawbody", e->raw_text);
}
/* Figure out calendar_body and plain_body */
if (DontSuppressQuoteMarkers) {
s = strstr(e->text, "%\"");
if (s) {
s += 2;
printf("\"calendar_body\":\"");
while (*s) {
if (*s == '%' && *(s+1) == '"') {
break;
}
PrintJSONChar(*s);
s++;
}
printf("\",");
}
}
s = strstr(e->text, "%\"");
if (s || e->is_color) {
printf("\"plain_body\":\"");
s = e->text;
if (e->is_color) {
while(*s && !isspace(*s)) s++;
while(*s && isspace(*s)) s++;
while(*s && !isspace(*s)) s++;
while(*s && isspace(*s)) s++;
while(*s && !isspace(*s)) s++;
while(*s && isspace(*s)) s++;
}
while(*s) {
if (*s == '%' && *(s+1) == '"') {
s += 2;
continue;
}
PrintJSONChar(*s);
s++;
}
printf("\",");
}
printf("\"body\":\"");
PrintJSONString(e->text);
printf("\"");
}
/***************************************************************/
/* */
/* WriteSimpleEntries */
/* */
/* Write entries in 'simple calendar' format. */
/* */
/***************************************************************/
static void WriteSimpleEntries(int col, int dse)
{
CalEntry *e = CalColumn[col];
CalEntry *n;
int y, m, d;
FromDSE(dse, &y, &m, &d);
while(e) {
if (DoPrefixLineNo) {
if (PsCal != PSCAL_LEVEL2 && PsCal != PSCAL_LEVEL3) {
printf("# fileinfo %d %s\n", e->lineno, e->filename);
}
}
if (PsCal >= PSCAL_LEVEL2) {
if (PsCal == PSCAL_LEVEL3) {
if (DidADay) {
printf(",\n");
}
}
DidADay = 1;
printf("{\"date\":\"%04d-%02d-%02d\",", y, m+1, d);
WriteSimpleEntryProtocol2(e);
printf("}");
if (PsCal != PSCAL_LEVEL3) {
printf("\n");
}
} else {
printf("%04d/%02d/%02d", y, m+1, d);
WriteSimpleEntryProtocol1(e);
}
free(e->text);
free(e->raw_text);
FreeTrigInfoChain(e->infos);
if (e->wc_text) free(e->wc_text);
n = e->next;
free(e);
e = n;
}
CalColumn[col] = NULL;
}
/***************************************************************/
/* */
/* Various functions for writing different types of lines. */
/* */
/***************************************************************/
static void WriteTopCalLine(void)
{
gon();
DRAW(br);
PrintCentered("", CalWidth-2, linestruct->lr);
DRAW(bl);
goff();
putchar('\n');
}
static void WriteBottomCalLine(void)
{
int i;
gon();
DRAW(tr);
for (i=0; i<7; i++) {
PrintCentered("", ColSpaces, linestruct->lr);
if (i != 6) {
DRAW(tlr);
} else {
DRAW(tl);
}
}
goff();
putchar('\n');
}
static void WritePostHeaderLine(void)
{
int i;
gon();
DRAW(tbr);
for (i=0; i<7; i++) {
PrintCentered("", ColSpaces, linestruct->lr);
if (i != 6) {
DRAW(blr);
} else {
DRAW(tbl);
}
}
goff();
putchar('\n');
}
static void WriteWeekHeaderLine(void)
{
int i;
gon();
DRAW(br);
for (i=0; i<7; i++) {
PrintCentered("", ColSpaces, linestruct->lr);
if (i != 6) {
DRAW(blr);
} else {
DRAW(bl);
}
}
goff();
putchar('\n');
}
static void WriteIntermediateCalLine(void)
{
int i;
gon();
DRAW(tbr);
for (i=0; i<7; i++) {
PrintCentered("", ColSpaces, linestruct->lr);
if (i != 6) {
DRAW(tblr);
} else {
DRAW(tbl);
}
}
goff();
putchar('\n');
}
static void WriteCalDays(void)
{
int i;
gon();
DRAW(tb);
goff();
for (i=0; i<7; i++) {
if (!MondayFirst)
PrintCentered(get_day_name((i+6)%7), ColSpaces, " ");
else
PrintCentered(get_day_name(i%7), ColSpaces, " ");
gon();
DRAW(tb);
goff();
}
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 */
/* */
/***************************************************************/
static char const *
CalendarTime(int tim, int duration)
{
static char buf[128];
int h, min, hh;
int h2, min2, hh2, newtim, days;
char const *ampm1;
char const *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) {
snprintf(daybuf, sizeof(daybuf), "+%d", days);
} else {
daybuf[0] = 0;
}
if (h >= 12) {
ampm1 = tr("pm");
} else {
ampm1 = tr("am");
}
if (h2 >= 12) {
ampm2 = tr("pm");
} else {
ampm2 = tr("am");
}
if (!days) {
if (!strcmp(ampm1, ampm2)) {
ampm1 = "";
}
}
switch(ScFormat) {
case SC_AMPM:
snprintf(buf, sizeof(buf), "%d%c%02d%s-%d%c%02d%s%s ",
hh, TimeSep, min, ampm1, hh2, TimeSep, min2, ampm2, daybuf);
break;
case SC_MIL:
snprintf(buf, sizeof(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 const *SimpleTime(int tim)
{
static char buf[128];
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;
snprintf(buf, sizeof(buf), "%d%c%02d%.64s ", hh, TimeSep, min, (h>=12) ? tr("pm") :
tr("am"));
}
break;
case SC_MIL:
if (tim != NO_TIME) {
h = tim / 60;
min = tim % 60;
snprintf(buf, sizeof(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, UntimedBeforeTimed) <= 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;
}
}
}
char const *SynthesizeTag(void)
{
struct MD5Context ctx;
unsigned char buf[16];
static char out[128];
MD5Init(&ctx);
MD5Update(&ctx, (unsigned char *) CurLine, strlen(CurLine));
MD5Final(buf, &ctx);
snprintf(out, sizeof(out), "__syn__%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
(unsigned int) buf[0], (unsigned int) buf[1],
(unsigned int) buf[2], (unsigned int) buf[3],
(unsigned int) buf[4], (unsigned int) buf[5],
(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]);
return out;
}