#include <stdio.h>
#include <time.h>
#define NAME "KOIKOI"
#define DOMAIN "royniang.com"
#define LOCATION "Bordeaux, France"
#define REPOPATH "https://git.intrfc.com/royniang/koikoi"
enum {
STRMEM = 4096 * 96,
GLOMEM = 100,
LEXMEM = 500,
HORMEM = 3500,
ITEMS = 64,
EPOCH = 2019
};
typedef struct Block Block;
typedef struct List List;
typedef struct Term Term;
typedef struct Log Log;
typedef struct Glossary Glossary;
typedef struct Lexicon Lexicon;
typedef struct Journal Journal;
struct Block {
int len;
char data[STRMEM];
};
struct List {
int len;
int routes;
char *name;
char *keys[ITEMS];
char *vals[ITEMS];
};
struct Term {
int body_len;
int gmni_len;
int children_len;
int incoming_len;
int outgoing_len;
int logs_len;
int events_len;
int ch;
int fh;
char *name;
char *host;
char *bref;
char *titl;
char *type;
char *relm;
char *date;
char *updt;
char *time;
char *utim;
char *filename;
char *date_from;
char *date_last;
char *body[ITEMS];
char *gmni[ITEMS];
struct List link;
struct Term *parent;
struct Term *children[ITEMS];
struct Term *incoming[ITEMS];
};
struct Log {
int code;
int pict;
char rune;
char *date;
char *ext;
char *name;
Term *term;
};
struct Glossary {
int len;
List lists[GLOMEM];
};
struct Lexicon {
int len;
Term terms[LEXMEM];
};
struct Journal {
int len;
Log logs[HORMEM];
};
#pragma mark - Helpers
int
cisp(char c)
{ /* char is space */
return c == ' ' || c == '\t' || c == '\n' || c == '\r';
}
int
cial(char c)
{ /* char is alpha */
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
}
int
cinu(char c)
{ /* char is num */
return c >= '0' && c <= '9';
}
char
clca(char c)
{ /* char to lowercase */
return c >= 'A' && c <= 'Z' ? c + ('a' - 'A') : c;
}
char
cuca(char c)
{ /* char to uppercase */
return c >= 'a' && c <= 'z' ? c - ('a' - 'A') : c;
}
int
spad(char *s, char c)
{ /* string count padding */
int i = 0;
while(s[i] && s[i] == c && s[++i]){
;
}
return i;
}
int
slen(char *s)
{ /* string length */
int i = 0;
while(s[i] && s[++i]){
;
}
return i;
}
char
*st__(char *s, char (*fn)(char))
{
int i = 0;
char c;
while((c = s[i])) s[i++] = fn(c);
return s;
}
char
*stuc(char *s)
{ /* string to uppercase */
return st__(s, cuca);
}
char
*stlc(char *s)
{ /* string to lowercase */
return st__(s, clca);
}
char
*scpy(char *src, char *dst, int len)
{ /* string copy */
int i = 0;
while((dst[i] = src[i]) && i < len - 2)
i++;
dst[i + 1] = '\0';
return dst;
}
int
scmp(char *a, char *b)
{ /* string compare */
int i = 0;
while(a[i] == b[i])
if(!a[i++])
return 1;
return 0;
}
int
sint(char *s, int len)
{ /* string to num */
int n = 0, i = 0;
while(s[i] && i < len && (s[i] >= '0' && s[i] <= '9'))
n = n * 10 + (s[i++] - '0');
return n;
}
char
*scsw(char *s, char a, char b)
{ /* string char swap */
int i = 0;
char c;
while((c = s[i]))
s[i++] = c == a ? b : c;
return s;
}
int
sian(char *s)
{ /* string is alphanum */
int i = 0;
char c;
while((c = s[i++]))
if(!cial(c) && !cinu(c) && !cisp(c))
return 0;
return 1;
}
int
scin(char *s, char c)
{ /* string char index */
int i = 0;
while(s[i])
if(s[i++] == c)
return i - 1;
return -1;
}
char
*scat(char *dst, const char *src)
{ /* string cat */
char *ptr = dst + slen(dst);
while(*src)
*ptr++ = *src++;
*ptr = '\0';
return dst;
}
int
ssin(char *s, char *ss)
{ /* string substring index */
int a = 0, b = 0;
while(s[a]){
if(s[a] == ss[b]){
if(!ss[b + 1])
return a - b;
b++;
}else
b = 0;
a++;
}
return -1;
}
char
*strm(char *s)
{
char *end;
while(cisp(*s))
s++;
if(*s == 0)
return s;
end = s + slen(s) - 1;
while(end > s && cisp(*end))
end--;
end[1] = '\0';
return s;
}
int
surl(char *s)
{ /* string is url */
return ssin(s, "://") >= 0 || ssin(s, "../") >= 0 || ssin(s, "mailto:") >= 0;
}
char
*sstr(char *src, char *dst, int from, int to)
{
int i;
char *a = (char *) src + from, *b = (char *) dst;
for(i = 0; i < to; i++)
b[i] = a[i];
dst[to] = '\0';
return dst;
}
int
afnd(char *src[], int len, char *val)
{
int i;
for(i = 0; i < len; i++)
if(scmp(src[i], val))
return i;
return -1;
}
char
*ccat(char *dst, char c)
{
int len = slen(dst);
dst[len] = c;
dst[len + 1] = '\0';
return dst;
}
#pragma mark - Core
int
error(char *msg, char *val)
{
printf("Error: %s (%s)\n", msg, val);
return 0;
}
int
errorid(char *msg, char *val, int id)
{
printf("Error: %s: %d (%s)\n", msg, id, val);
return 0;
}
#pragma mark - Block
char *
push(Block *b, char *s)
{
int i = 0, o = b->len;
while (s[i])
b->data[b->len++] = s[i++];
b->data[b->len++] = '\0';
return &b->data[o];
}
#pragma mark - List
List *
makelist(List *l, char *name)
{
l->len = 0;
l->routes = 0;
l->name = stlc(name);
return l;
}
List *
findlist(Glossary *glo, char *name)
{
int i;
scsw(stlc(name), '_', ' ');
for(i = 0; i < glo->len; ++i)
if(scmp(name, glo->lists[i].name))
return &glo->lists[i];
return NULL;
}
#pragma mark - Term
Term *
maketerm(Term *t, char *name)
{
t->body_len = 0;
t->gmni_len = 0;
t->children_len = 0;
t->incoming_len = 0;
t->outgoing_len = 0;
t->logs_len = 0;
t->events_len = 0;
t->ch = 0;
t->fh = 0;
t->name = stlc(name);
return t;
}
Term *
findterm(Lexicon *lex, char *name)
{
int i;
scsw(stlc(name), '_', ' ');
for(i = 0; i < lex->len; ++i)
if(scmp(name, lex->terms[i].name))
return &lex->terms[i];
return NULL;
}
char *
statusterm(Term *t)
{
if(t->type && scmp(t->type, "alias"))
return "alias";
if(t->type && scmp(t->type, "log"))
return "log";
if(t->body_len < 1)
return "stub";
if(t->incoming_len < 1)
return "orphan";
if(t->outgoing_len < 1)
return "deadend";
if(t->logs_len < 2)
return "article";
return "";
}
#pragma mark - Log
Log *
makelog(Log *l, char *date)
{
l->code = 0;
l->pict = 0;
l->date = date;
return l;
}
Log *
finddiary(Journal *jou, Term *t, int deep)
{
int i;
for(i = 0; i < jou->len; ++i)
if(jou->logs[i].term == t && jou->logs[i].pict > 0)
return &jou->logs[i];
if(deep)
for(i = 0; i < t->children_len; ++i)
return finddiary(jou, t->children[i], 0);
return NULL;
}
#pragma mark - File
FILE *
getfile(char *dir, char *filename, char *ext, char *op)
{
char filepath[1024];
filepath[0] = '\0';
scat(filepath, dir);
scat(filepath, filename);
scat(filepath, ext);
scat(filepath, "\0");
return fopen(filepath, op);
}
#pragma mark - Time
void
fpRFC2822(FILE *f, time_t t, char *time, char *utim)
{
struct tm *tm = localtime(&t);
char *days[7] = {
"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
};
char *months[12] = {
"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul",
"Aug", "Sep", "Oct", "Nov", "Dec"
};
fprintf(f, "%s, %02d %s %d ", days[tm->tm_wday], tm->tm_mday, months[tm->tm_mon], tm->tm_year + 1900);
if(time)
fprintf(f, "%s", time);
else if(utim)
fprintf(f, "%s", utim);
else
fputs("00", f);
fputs(":00 +0100", f);
}
void
fpRFC3339(FILE *f, time_t t, int full, char *time, char *utim)
{
struct tm *tm = localtime(&t);
fprintf(f, "%04d-%02d-%02d", tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
if((time && full) || (utim && full))
fputs("T", f);
if((!full && time) || (!full && utim))
fputs(" ", f);
if(time)
fprintf(f, "%s", time);
else if(utim)
fprintf(f, "%s", utim);
else if(full)
fprintf(f, "T%02d:%02d", tm->tm_hour, tm->tm_min);
if(full || (full && time) || (full && utim))
fprintf(f, ":%02d+01:00", tm->tm_sec);
}
time_t
ymdstrtime(int y, int m, int d)
{
struct tm stime;
stime.tm_year = y - 1900;
stime.tm_mday = d;
stime.tm_mon = m;
stime.tm_hour = 0;
stime.tm_min = 0;
stime.tm_sec = 1;
stime.tm_isdst = -1;
return mktime(&stime);
}
int
arveliedays(char *date)
{
int year = (date[0] - '0') * 10 + (date[1] - '0');
int dotm = ((date[3] - '0') * 10) + date[4] - '0';
int moty = date[2] == '+' ? 26 : date[2] - 'A';
return year * 365 + moty * 14 + dotm;
}
void
parvelie(int epoch)
{
time_t now;
struct tm *local;
time(&now);
local = localtime(&now);
printf("%02d%c%02d",
(1900 + local->tm_year - epoch) % 100,
local->tm_yday >= 364 ? '+' : 'A' + local->tm_yday / 14,
local->tm_yday % 14);
}
time_t
dotytime(int y, int doty)
{
int months[13] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
int m = 0;
int d = 0;
int yd = 0;
if((y % 4) || ((y % 100) && (y % 400)))
months[1] = months[1] + 1;
for(m = 0; m < 12; ++m){
yd += months[m];
d = months[m] - (yd - doty);
if (yd > doty)
break;
}
return ymdstrtime(y, m, d);
}
time_t
arvelietime(int epoch, char *date)
{
int year = epoch + (date[0] - '0') * 10 + (date[1] - '0');
int dotm = ((date[3] - '0') * 10) + date[4] - '0';
int moty = date[2] == '+' ? 26 : date[2] - 'A';
int doty = moty * 14 + dotm + 1;
return dotytime(year, doty);
}
#pragma mark - Fprint
void
fplifeline(FILE *f, Journal *jou, Term *t, int gmi)
{
int limit_from = arveliedays(jou->logs[jou->len - 1].date);
int limit_to = arveliedays(jou->logs[0].date) - limit_from;
int range_from = arveliedays(t->date_from) - limit_from;
int range_to = arveliedays(t->date_last) - limit_from;
int i, period = (limit_to - limit_from) / 5;
int a = range_from / period, b = range_to / period;
if(!gmi)
fputs("<span title='Epoch' class='raw' aria-hidden='true'>", f);
for(i = 0; i < 6; i++)
fputs(i >= a && i <= b ? "■" : "□", f);
if(!gmi)
fputs("</span>", f);
}
void
fppict(FILE *f, int pict, char *ext, char *host, char *name, int caption, char *link)
{
fputs("<figure class='logpic'>", f);
fprintf(f, "<img src='media/diary/%d.%s' alt='%s picture' loading='lazy' width='576'/>", pict, ext, name);
if(caption){
fputs("<figcaption>", f);
if(link)
fprintf(f, "<a href='%s.html'>%s</a> — %s", link, host, name);
else
fprintf(f, "%s — %s", host, name);
fputs("</figcaption>", f);
}
fputs("</figure>", f);
}
void
fpgmipict(FILE *f, int pict, char *ext, char *host, char *name, int caption)
{
fprintf(f, "=> media/diary/%d.%s\t📷", pict, ext);
if(caption)
fprintf(f, " %s — %s", host, name);
fputs("\n", f);
}
void
fplogpict(FILE *f, Log *l, int caption, int gmi)
{
if(gmi)
fpgmipict(f, l->pict, l->ext, l->date, l->name, caption);
else
fppict(f, l->pict, l->ext, l->date, l->name, caption, NULL);
}
void
fptemplatelink(FILE *f, Lexicon *lex, Term *t, char *s, int gmi)
{
int split = scin(s, ' ');
char target[256], name[256];
/* find target and name */
if(split == -1)
scpy(sstr(s, target, 0, slen(s)), name, 256);
else{
sstr(s, target, 0, split);
sstr(s, name, split + 1, slen(s) - split);
}
/* output */
if(surl(target)){
if(f && gmi)
fprintf(f, "=>\t%s\t%s\n", target, name);
else if(f)
fprintf(f, "<a href='%s' class='external'>%s</a>", target, name);
}else{
Term *tt = findterm(lex, target);
if(!tt)
error("Unknown link", target);
if(f && gmi)
fprintf(f, "=>%s.gmi\t%s — %s\n", tt->filename, name, tt->bref);
else if(f)
fprintf(f, "<a href='%s.html'>%s</a>", tt->filename, name);
else if(!scmp(t->name, "index")){
tt->incoming[tt->incoming_len++] = t;
t->outgoing_len++;
}
}
}
int
fplist(FILE *f, Glossary *glo, char *target, int gmi)
{
int j;
List *l = findlist(glo, target);
if(!l)
return error("Unknown list", target);
if(gmi)
fprintf(f, "\n## %s\n", l->name);
else{
fprintf(f, "<p class='metaheading'>%s</p>", l->name);
fputs("<ul class='gloss'>", f);
}
for(j = 0; j < l->len; ++j){
if(!l->keys[j]){
if(gmi)
fprintf(f, "* %s\n", l->vals[j]);
else
fprintf(f, "<li>%s</li>", l->vals[j]);
}else if(surl(l->vals[j])){
if(gmi)
fprintf(f, "=>\t%s\t%s\n", l->vals[j], l->keys[j]);
else
fprintf(f, "<li><a href='%s' class='external'>%s</a></li>", l->vals[j], l->keys[j]);
}else{
if(gmi)
fprintf(f, "* %s: %s\n", l->keys[j], l->vals[j]);
else
fprintf(f, "<li><b>%s</b>: %s</li>", l->keys[j], l->vals[j]);
}
}
if(!gmi)
fputs("</ul>", f);
l->routes++;
return 1;
}
int
fpdef(FILE *f, Glossary *glo, char *target, int gmi)
{
int j;
List *l = findlist(glo, target);
if(!l)
return error("Unknown definition", target);
if(gmi)
fprintf(f, "\n## %s\n", l->name);
else{
fprintf(f, "<p class='metaheading'>%s</p>", l->name);
fputs("<dl class='def'>", f);
}
for(j = 0; j < l->len; ++j){
if(!l->keys[j])
return error("Not a definition list", target);
else{
if(gmi)
fprintf(f, "* %s: %s\n", l->keys[j], l->vals[j]);
else
fprintf(f, "<dt>%s</dt><dd>%s</dd>", l->keys[j], l->vals[j]);
}
}
if(!gmi)
fputs("</dl>", f);
l->routes++;
return 1;
}
void
fpheading(FILE *f, char *target, int h3, int gmi)
{
char name[256];
scpy(target, name, 256);
if(gmi){
if(h3)
fprintf(f, "\n### %s\n", target);
else
fprintf(f, "\n## %s\n", target);
}else{
scsw(stlc(target), ' ', '_');
if(h3)
fprintf(f, "<h3 id='%s'><a href='#%s'>%s</a></h3>", target, target, name);
else
fprintf(f, "<h2 id='%s'><a href='#%s'>%s</a></h2>", target, target, name);
}
}
void
fpimg(FILE *f, char *target, int gmi)
{ /* TODO */
int split2 = scin(target, ' ');
int split3 = scin(target, '|');
char param[256], alt[256], width[256];
sstr(target, param, 0, split2);
if(split3 > 0 && split2 > 0){
sstr(target, width, split3 + 1, slen(target));
sstr(target, alt, split2 + 1, slen(target) - split2 - 5);
fprintf(f, "<figure><img src='media/%s' width='%s' alt='%s'/>"
"<figcaption><b>fig.</b> %s</figcaption></figure>",
param, width, alt, alt);
}else if(split2 > 0){
sstr(target, alt, split2 + 1, slen(target) - split2);
if(gmi)
fprintf(f, "=>\tmedia/%s\t📷 %s\n", param, alt);
else
fprintf(f, "<figure><img src='media/%s' alt='%s'/>"
"<figcaption><b>fig.</b> %s</figcaption></figure>",
param, alt, alt);
}else if(gmi)
fprintf(f, "=>\tmedia/%s\t📷 %s\n", target, target);
else
fprintf(f, "<img src='media/%s' alt=''/> ", target);
}
int
fpinclude(FILE *f, char *target, int text, int gmi, int req)
{
int lines = 0;
char c;
char *folder = text ? "inc/text/" : gmi ? "inc/gmi/" : "inc/html/";
char *ext = text ? ".txt" : gmi ? ".gmi" : ".htm";
FILE *fp = getfile(folder, target, ext, "r");
if(!fp){
if(req)
return error("Missing include ", target);
else
return 0;
}
if(text && gmi)
fputs("\n```", f);
else if(text)
fputs("<figure><pre><code>", f);
while((c = fgetc(fp)) != EOF){
if(text && gmi){
if(c == '`')
fputs("`", f);
else
fputc(c, f);
}else if(text){
if(c == '<')
fputs("<", f);
else if(c == '>')
fputs(">", f);
else if(c == '&')
fputs("&", f);
else
fputc(c, f);
}else
fputc(c, f);
if(c == '\n')
lines++;
}
fclose(fp);
if(text && gmi)
fputs("```\n", f);
else if(text){
fputs("</code></pre>", f);
fprintf(f, "<figcaption><a href='" REPOPATH "/src/branch/front/%s%s%s' class='external'>Source</a>; "
"<a href='%s%s%s'>%s%s</a> (%d lines)</figcaption>\n",
folder, target, ext, folder, target, ext, target, ext, lines);
fputs("</figure>", f);
}
return 1;
}
void
fpmodule(FILE *f, Glossary *glo, char *s)
{
int split = scin(s, ' ');
char cmd[256], target[256];
sstr(s, cmd, 1, split - 1);
sstr(s, target, split + 1, slen(s) - split);
if(scmp(cmd, "youtube"))
fprintf(f, "<iframe width='600' height='380' "
"src='https://www.youtube.com/embed/%s?rel=0' "
"style='max-width:700px' frameborder='0' allow='autoplay; "
"encrypted-media' allowfullscreen></iframe>",
target);
else if(scmp(cmd, "redirect"))
fprintf(f, "<meta http-equiv='refresh' content='2; url=%s.html'/>"
"<p>In a hurry? Travel to <a href='%s.html'>%s</a>.</p>",
target, target, target);
else if(scmp(cmd, "list"))
fplist(f, glo, target, 0);
else if(scmp(cmd, "glist"))
fplist(f, glo, target, 1);
else if(scmp(cmd, "def"))
fpdef(f, glo, target, 0);
else if(scmp(cmd, "gdef"))
fpdef(f, glo, target, 1);
else if(scmp(cmd, "text"))
fpinclude(f, target, 1, 0, 1);
else if(scmp(cmd, "gtext"))
fpinclude(f, target, 1, 1, 1);
else if(scmp(cmd, "html"))
fpinclude(f, target, 0, 0, 1);
else if(scmp(cmd, "gmi"))
fpinclude(f, target, 0, 1, 1);
else if(scmp(cmd, "br"))
fputs("\n", f);
else if(scmp(cmd, "h2"))
fpheading(f, target, 0, 0);
else if(scmp(cmd, "gh2"))
fpheading(f, target, 0, 1);
else if(scmp(cmd, "h3"))
fpheading(f, target, 1, 0);
else if(scmp(cmd, "gh3"))
fpheading(f, target, 1, 1);
else if(scmp(cmd, "img"))
fpimg(f, target, 0);
else if(scmp(cmd, "gimg"))
fpimg(f, target, 1);
else if(scmp(cmd, "com")){
fprintf(f, "<!-- %s -->", target);
printf("Needs editing: %s\n", target);
}else
printf("Warning: Missing template mod (%s)\n", s);
}
void
fptemplate(FILE *f, Glossary *glo, Lexicon *lex, Term *t, char *s, int gmi)
{
int i, capture = 0;
char buf[1024];
buf[0] = '\0';
for(i = 0; i < slen(s); ++i){
char c = s[i];
if(c == '}'){
capture = 0;
if(buf[0] == '^' && f)
fpmodule(f, glo, buf);
else if(buf[0] != '^' && gmi)
fptemplatelink(f, lex, t, buf, 1);
else if(buf[0] != '^')
fptemplatelink(f, lex, t, buf, 0);
}
if(capture)
ccat(buf, c);
else if(c != '{' && c != '}' && f)
fputc(c, f);
if(c == '{'){
capture = 1;
buf[0] = '\0';
}
}
}
void
fpbodypart(FILE *f, Glossary *glo, Lexicon *lex, Term *t, int gmi)
{
int i;
if(gmi)
for(i = 0; i < t->gmni_len; ++i){
fptemplate(f, glo, lex, t, t->gmni[i], 1);
fputs("\n", f);
}
else
for(i = 0; i < t->body_len; ++i)
fptemplate(f, glo, lex, t, t->body[i], 0);
}
void
fpgmiheader(FILE *f, Term *t)
{
fputs("# ", f);
if(t->relm && scmp(t->relm, "9"))
fputs("⑨", f);
if(t->relm && scmp(t->relm, "obsd"))
fputs("🐡", f);
else if(t->relm && scmp(t->relm, "photography"))
fputs("🎞️", f);
else if(scmp(t->name, "death"))
fputs("⚰️", f);
else if(scmp(t->name, "logs"))
fputs("📝", f);
else if(t->relm && scmp(t->relm, "time"))
fputs("⏳", f);
else if((t->relm && scmp(t->relm, "what")) || scmp(t->name, "about") ||
scmp(t->name, "roy niang"))
fputs("🤔", f);
fprintf(f, " %s\n%s\n\n", t->titl, t->bref);
if(t->date){
fputs("📅 Published: ", f);
if(t->time)
fpRFC3339(f, arvelietime(EPOCH, t->date), 0, t->time, 0);
else
fpRFC3339(f, arvelietime(EPOCH, t->date), 0, 0, 0);
fprintf(f, " (%s)\n", t->date);
}
if(t->updt){
fputs("📆 Updated: ", f);
if(t->utim)
fpRFC3339(f, arvelietime(EPOCH, t->updt), 0, 0, t->utim);
else
fpRFC3339(f, arvelietime(EPOCH, t->updt), 0, 0, 0);
fprintf(f, " (%s)\n", t->updt);
}
if(t->relm && scmp(t->relm, "solo")){
if(t->body_len != 0)
fprintf(f, "=>\thttps://" DOMAIN "/%s.html\tWeb version\n", t->filename);
fputs("=>\tmailto:roy@royniang.com\troy@royniang.com\n\n\n", f);
}
}
void
fplogo(FILE *f, Term *t)
{
FILE *logo;
int mask;
if(t->relm && scmp(t->relm, "9"))
logo = fopen("priv/identity/9.svg", "r");
else if(scmp(t->name, "death"))
logo = fopen("priv/identity/no.svg", "r");
else if(t->relm && scmp(t->relm, "what"))
logo = fopen("priv/identity/what.svg", "r");
else if(t->relm && scmp(t->relm, "low"))
logo = fopen("priv/identity/low.svg", "r");
else if(t->relm && scmp(t->relm, "high"))
logo = fopen("priv/identity/high.svg", "r");
else if(t->relm && scmp(t->relm, "photography"))
logo = fopen("priv/identity/vf.svg", "r");
else if(scmp(t->name, "about"))
logo = fopen("static/shared/identity/koikoi.svg", "r");
else
logo = fopen("static/shared/identity/mask.svg", "r");
if(!logo){
printf("Warning: Could not open logo", t->name);
logo = fopen("static/shared/identity/mask.svg", "r");
}
while((mask = fgetc(logo)) != EOF)
fputc(mask, f);
fclose(logo);
}
void
fpbanner(FILE *f, Journal *jou, Term *t, int caption, int gmi)
{
Log *l = finddiary(jou, t, 0);
if(l && gmi)
fplogpict(f, l, caption, 1);
else if(l)
fplogpict(f, l, caption, 0);
}
void
fpnavsub(FILE *f, Term *t, Term *target)
{
int i;
fputs("<ul>", f);
for(i = 0; i < t->children_len; ++i){
Term *tc = t->children[i];
if(tc->name == t->name)
continue; /* Paradox */
if(tc->type && scmp(tc->type, "hidden"))
continue;
if(tc->type && scmp(tc->type, "alias"))
continue;
if(tc->type && scmp(tc->type, "log"))
continue;
fputs("<li><a", f);
if(tc->name == target->name)
fputs(" class='opened'", f);
fprintf(f, " href='%s.html'>%s</a></li>", tc->filename, tc->name);
}
fputs("</ul>", f);
}
void
fpnav(FILE *f, Term *t)
{
if(!t->parent)
error("Missing parent ", t->name);
if(!t->parent->parent)
error("Missing parent ", t->parent->name);
fputs("<div id='nav'><p>Crumbs</p><nav>", f);
if(t->parent->parent->name == t->parent->name)
fpnavsub(f, t->parent->parent, t);
else
fpnavsub(f, t->parent->parent, t->parent);
if(t->parent->parent->name != t->parent->name)
fpnavsub(f, t->parent, t);
if(t->parent->name != t->name)
fpnavsub(f, t, t);
fputs("</nav></div>", f);
}
void
fplogmeta(FILE *f, Term *t)
{
fputs("<ul class='bmeta'><li class='date'>Published: "
"<time class='dt-published' datetime='", f);
fpRFC3339(f, arvelietime(EPOCH, t->date), 1, t->time, 0);
fputs("'>", f);
fpRFC3339(f, arvelietime(EPOCH, t->date), 0, t->time, 0);
fputs("</time></li>", f);
if(t->updt){
fputs("<li class='date'>Updated: <time datetime='", f);
fpRFC3339(f, arvelietime(EPOCH, t->updt), 1, 0, t->utim);
fputs("'>", f);
fpRFC3339(f, arvelietime(EPOCH, t->updt), 0, 0, t->utim);
fputs("</time></li>", f);
}
fprintf(f, "<li>Author: <a class='p-author h-card' href='https://" DOMAIN "'>roy niang</a></li>"
"<li>Parent page: <a href='%s.html'>%s</a></li>"
"<li><a class='u-url' href='https://" DOMAIN "/%s.html'>Permalink</a></li>",
t->host, t->host, t->filename);
fputs("<li>Questions or corrections: "
"<a class='u-email' href='mailto:roy@royniang.com'>roy@royniang.com</a></li>"
"</ul><hr class='bmeta'>", f);
}
void
fpbody(FILE *f, Glossary *glo, Lexicon *lex, Term *t, int gmi)
{
if(!gmi){
fputs("<article class='h-entry'>", f);
fprintf(f, "<p class='p-summary'>%s</p><h1 class='p-name'>%s</h1>", t->bref, t->titl);
if(t->date)
fplogmeta(f, t);
fputs("<div class='e-content'>", f);
fpbodypart(f, glo, lex, t, 0);
fputs("</div>", f);
if(t->gmni_len != 0 && t->type && scmp(t->type, "log"))
fprintf(f, "<figure><aside class='small'><p>This document is also on "
"<a rel='syndication' class='u-syndication' href='gemini://"
"" DOMAIN "/%s.gmi'>gemini</a>.</p></aside></figure>",
t->filename);
fputs("</article>", f);
}else if(t->gmni_len == 0){
fputs("The gemini version of this document is a stub. "
"You may have more chances with the web version.\n", f);
fprintf(f, "=>\thttps://" DOMAIN "/%s.html\t%s\n", t->filename, t->name);
}else
fpbodypart(f, glo, lex, t, 1);
}
void
fpgminavsub(FILE *f, Term *t)
{
int i;
for(i = 0; i < t->children_len; ++i){
Term *tc = t->children[i];
if(tc->type && scmp(tc->type, "alias"))
continue;
if(tc->type && scmp(tc->type, "hidden"))
continue;
if(tc->gmni_len == 0)
continue;
if(tc->date){
fprintf(f, "=>\t%s.gmi\t", tc->filename);
fpRFC3339(f, arvelietime(EPOCH, tc->date), 0, 0, 0);
fprintf(f, ": %s — %s\n", tc->titl, tc->bref);
}else
fprintf(f, "=>\t%s.gmi\t↪️ %s — %s\n", tc->filename, tc->titl, tc->bref);
}
}
void
fpgmiportal(FILE *f, Term *t, Journal *jou)
{
int i;
for(i = 0; i < t->children_len; ++i){
Term *tc = t->children[i];
Log *l = finddiary(jou, tc, 1);
if(l)
fpgmipict(f, l->pict, l->ext, tc->name, tc->bref, 1);
}
}
void
fpgminav(FILE *f, Term *t)
{
int i, child = 0;
for(i = 0; i < t->children_len; ++i){
Term *tc = t->children[i];
if(tc->type && scmp(tc->type, "alias"))
continue;
if(tc->type && scmp(tc->type, "hidden"))
continue;
if(tc->gmni_len == 0)
continue;
if(tc->gmni_len > 0)
child++;
}
if(child != 0 && scmp(t->name, "logs"))
fprintf(f, "\n\n## %d Entries\n\n", child);
else if(child != 0)
fputs("\n\n## Child Documents\n\n", f);
fpgminavsub(f, t);
if(scmp(t->name, "logs"))
fputs("\n=>\tatom.xml\tAtom feed\n", f);
}
void
fpportal(FILE *f, Glossary *glo, Lexicon *lex, Journal *jou, Term *t, int pict, int text, int card)
{
int i;
if (card)
fputs("<p class='metaheading'>Child Documents</p><div id='index'>", f);
for(i = 0; i < t->children_len; ++i){
Term *tc = t->children[i];
if(pict){
Log *l = finddiary(jou, tc, 1);
if(l)
fppict(f, l->pict, l->ext, tc->name, tc->bref, 1, tc->filename);
}
if(card){
fputs("<div>", f);
fprintf(f, "<dl><dt><a href='%s.html'>%s</a></dt>", tc->filename, tc->titl);
if(!text)
fprintf(f, "<dd>%s</dd>", tc->bref);
fputs("</dl></div>", f);
}
}
if(card)
fputs("</div>", f);
if(text){
for(i = 0; i < t->children_len; ++i){
Term *tc = t->children[i];
fprintf(f, "<h2><a href='%s.html'>%s</a></h2>", tc->filename, tc->titl);
fprintf(f, "<p><i>%s</i></p>", tc->bref);
fpbodypart(f, glo, lex, tc, 0);
}
}
}
void
fplogs(FILE *f, Term *t)
{
int i;
fputs("<p class='metaheading'>Entries</p><ul id='loglist'>", f);
for(i = 0; i < t->children_len; ++i){
Term *tc = t->children[i];
if(!tc->date)
error("Missing datetime ", tc->name);
fputs("<li>", f);
fprintf(f, "<a href='%s.html'>%s</a>", tc->filename, tc->titl);
fputs("<time datetime='", f);
fpRFC3339(f, arvelietime(EPOCH, tc->date), 1, t->time, 0);
fputs("'>", f);
fpRFC3339(f, arvelietime(EPOCH, tc->date), 0, 0, 0);
fputs("</time>", f);
fprintf(f, "%s", tc->bref);
fputs("</li>", f);
}
fputs("</ul>", f);
}
void
fpalbum(FILE *f, Journal *jou, Term *t, int gmi)
{
int i;
for(i = 0; i < jou->len; ++i){
Log l = jou->logs[i];
if(l.term != t || l.pict < 1 || l.pict == finddiary(jou, t, 0)->pict)
continue;
if(gmi){
fplogpict(f, &l, 1, 1);
}else
fplogpict(f, &l, 1, 0);
}
}
void
fplinks(FILE *f, Term *t, int gmi)
{
int i;
if(t->link.len < 1)
return;
if(gmi)
fputs("\n\n### Links\n\n", f);
else{
fputs("<div><p class='metaheading'>Links</p><ul style='list-style: none; margin-left:0'>", f);
}
for(i = 0; i < t->link.len; ++i){
if(gmi)
fprintf(f, "=>\t%s\t%s\n", t->link.vals[i], t->link.keys[i]);
else
fprintf(f, "<li><a href='%s' class='external'>%s</a></li>", t->link.vals[i], t->link.keys[i]);
}
if(!gmi)
fputs("</ul></div>", f);
}
void
fpincoming(FILE *f, Term *t, int gmi)
{
int i;
if(t->incoming_len < 1)
return;
if(gmi)
fputs("\n\n### ", f);
else
fputs("<div><p class='metaheading'>", f);
fprintf(f, "%d Incoming Links\n\n", t->incoming_len);
if(!gmi)
fputs("</p><p>", f);
for(i = 0; i < t->incoming_len; ++i){
if(gmi)
fprintf(f, "=>\t%s.gmi\t%s\n", t->incoming[i]->filename, t->incoming[i]->name);
else
fprintf(f, "<a href='%s.html' class='incoming'>%s</a> ", t->incoming[i]->filename, t->incoming[i]->name);
}
if(!gmi)
fputs("</p></div>", f);
}
void
fpevents(FILE *f, Journal *jou, Term *t, int gmi)
{
int i;
if(!gmi)
fputs("<dl>", f);
for(i = 0; i < jou->len; ++i){
Log *l = &jou->logs[i];
if(l->rune != '+')
continue;
if(l->term != t && l->term->parent != t)
continue;
if(gmi)
fprintf(f, "* %s — %s\n", l->date, l->name);
else
fprintf(f, "<dt class='numbers'>%s</dt><dd>%s</dd>", l->date, l->name);
}
if(!gmi)
fputs("</dl>", f);
}
void
fphoraire(FILE *f, Journal *jou, Term *t, int gmi)
{
if(t->logs_len < 2 || !t->date_last)
return;
if(gmi){
fputs("\n\n### Updates\n\n", f);
fplifeline(f, jou, t, 1);
fprintf(f, "\nUpdated on %s, edited %d times (+%d/%dfh).\n", t->date_last, t->logs_len, t->ch, t->fh);
}else{
fputs("<div><p class='metaheading'>Updates</p><p>", f);
fplifeline(f, jou, t, 0);
fprintf(f, "<br>Updated on <a href='tracker.html' class='numbers'>%s</a>, "
"edited %d times (+%d/%dfh).",
t->date_last, t->logs_len, t->ch, t->fh);
fputs("</p>", f);
}
if(t->events_len && gmi)
fpevents(f, jou, t, 1);
else if(t->events_len)
fpevents(f, jou, t, 0);
if(!gmi)
fputs("</div>", f);
}
void
fphome(FILE *f, Journal *jou, int gmi)
{
int i, events = 0;
for(i = 0; i < 5; ++i){
if(jou->logs[i].rune == '+'){
events = 1;
break;
}
}
if(!events)
return;
if(gmi)
fputs("## 🗓️ Events\n\n", f);
else{
fputs("<div class='metaheading'>Events</div>", f);
fputs("<dl>", f);
}
for(i = 0; i < 5; ++i){
if(jou->logs[i].rune != '+')
continue;
if(gmi)
fprintf(f, "=>\t%s.gmi\t%s — %s: %s\n",
jou->logs[i].term->filename,
jou->logs[i].date,
jou->logs[i].term->name,
jou->logs[i].name);
else
fprintf(f, "<dt><a href='%s.html'>%s — %s</a></dt><dd>%s</dd>",
jou->logs[i].term->filename,
jou->logs[i].date,
jou->logs[i].term->name,
jou->logs[i].name);
}
if(!gmi)
fputs("</dl>", f);
}
void
fpcalendar(FILE *f, Journal *jou, int gmi)
{
int i, last_year = 0;
if(gmi)
fputs("\n", f);
else
fputs("<dl>", f);
for(i = 0; i < jou->len; ++i){
if(jou->logs[i].rune != '+')
continue;
if(last_year != sint(jou->logs[i].date, 2) && gmi)
fputs("\n", f);
else if(last_year != sint(jou->logs[i].date, 2) && !gmi)
fputs("</dl><hr><dl>", f);
if(gmi)
fprintf(f, "=>\t%s.gmi\t%s — %s: %s\n",
jou->logs[i].term->filename,
jou->logs[i].date,
jou->logs[i].term->name,
jou->logs[i].name);
else
fprintf(f, "<dt><a href='%s.html' class='numbers'>%s — %s</a></dt><dd>%s</dd>",
jou->logs[i].term->filename,
jou->logs[i].date,
jou->logs[i].term->name,
jou->logs[i].name);
last_year = sint(jou->logs[i].date, 2);
}
if(!gmi)
fputs("</dl>", f);
}
void
fptracker(FILE *f, Journal *jou, int gmi)
{
char *known[LEXMEM];
int i, known_id = 0, last_year = 20, offset = arveliedays(jou->logs[0].date);
if(!gmi)
fputs("<dl>", f);
for(i = 0; i < jou->len; ++i){
Log *l = &jou->logs[i];
if(offset - arveliedays(l->date) < 0)
continue;
if(afnd(known, known_id, l->term->name) > -1)
continue;
if(last_year != sint(l->date, 2) && gmi)
fputs("\n", f);
else if(last_year != sint(l->date, 2))
fputs("</dl><hr><dl>", f);
if(gmi){
fprintf(f, "=>\t%s.gmi\t", l->term->filename);
fplifeline(f, jou, l->term, 1);
fprintf(f, " %s — %s\n", l->date, l->term->name);
}else{
fprintf(f, "<dt><a href='%s.html' class='numbers'>%s</a></dt>"
"<dd>Last update: <span class='numbers'>%s</span><br>",
l->term->filename,
l->term->name,
l->date);
fplifeline(f, jou, l->term, 0);
fputs("</dd>", f);
}
last_year = sint(l->date, 2);
known[known_id] = l->term->name;
known_id++;
}
if(!gmi)
fputs("</dl>", f);
}
void
fpjournal(FILE *f, Journal *jou)
{
int i, count = 0;
for(i = 0; i < jou->len; ++i){
if(count > 20)
break;
if(jou->logs[i].pict == 0)
continue;
fplogpict(f, &jou->logs[i], 1, 0);
count++;
}
}
void
fpgmijournal(FILE *f, Journal *jou)
{
int i, count = 0;
for(i = 0; i < jou->len; ++i){
if(count > 20)
break;
if(jou->logs[i].pict == 0)
continue;
fplogpict(f, &jou->logs[i], 1, 1);
fputs("\n", f);
count++;
}
fclose(f);
}
void
fpnow(FILE *f, Lexicon *lex, Journal *jou, int gmi)
{
int i, projects_len = 0;
char *pname[56], *pfname[56];
double sum_value = 0, pval[56], pmaxval = 0;
time_t now;
time(&now);
for(i = 0; i < 56; ++i){
int index = 0;
Log l = jou->logs[i];
if(arveliedays(l.date) < 56)
break;
if(l.code % 10 < 1)
continue;
index = afnd(pname, projects_len, l.term->name);
if(index < 0){
index = projects_len;
pname[index] = l.term->name;
pfname[index] = l.term->filename;
pval[index] = 0;
projects_len++;
}
pval[index] += l.code % 10;
sum_value += l.code % 10;
}
if(gmi)
fputs("\n## Evaluated input\n", f);
else
fputs("<p class='metaheading'>Evaluated input</p>", f);
for(i = 0; i < projects_len; ++i){
/* find most active with a photo */
if(finddiary(jou, findterm(lex, pname[i]), 0) && pval[i] > pmaxval)
pmaxval = pval[i];
}
for(i = 0; i < projects_len; ++i){
if(pval[i] != pmaxval)
continue;
if(gmi)
fplogpict(f, finddiary(jou, findterm(lex, pname[i]), 0), 1, 1);
else
fplogpict(f, finddiary(jou, findterm(lex, pname[i]), 0), 1, 0);
break;
}
if(!gmi)
fputs("<p>", f);
fprintf(f, "This data shows the distribution of %.0f hours over %d projects, "
"recorded during the last %d days, for an average of %.1f work hours "
"per day and %.1f work hours per project.",
sum_value, projects_len, 56, sum_value / 56, sum_value / projects_len);
if(gmi)
fputs("\n", f);
else
fputs("</p>", f);
for(i = 0; i < projects_len; ++i){
if(gmi)
fprintf(f, "=>\t%s.gmi\t%s: %.2f%%\n", pfname[i], pname[i], pval[i] / sum_value * 100);
else{
fputs("<div class='tabs'>", f);
fprintf(f, "<span><b><a href='%s.html'>%s</a></b></span> "
"<span>%.2f%</span>",
pfname[i], pname[i], pval[i] / sum_value * 100);
fputs("</div>", f);
}
}
if(!gmi)
fputs("<p>", f);
fprintf(f, "Last generated on %s (" LOCATION ").\n", ctime(&now));
if(!gmi)
fputs("</p>", f);
}
void
fpindexsub(FILE *f, Term *t, int depth)
{
int i;
if(t->type && (scmp(t->type, "hidden") || (scmp(t->type, "alias"))))
;
else{
fprintf(f, "<li><a href='%s.html'>%s</a>", t->filename, t->name);
if (!scmp(statusterm(t), "\0"))
fprintf(f, " <small>(%s)</small>", statusterm(t));
if(t->children_len < 1){
fputs("</li>", f);
return;
}
}
for(i = 0; i < t->children_len; ++i){
if(!scmp(t->children[i]->name, t->name)){
fputs("<ul>", f);
fpindexsub(f, t->children[i], depth++);
fputs("</ul>", f);
}
}
}
void
fpindex(FILE *f, Lexicon *lex, Journal *jou, int gmi)
{
int i, sends = 0, stubs = 0, orphans = 0, deadends = 0;
for(i = 0; i < lex->len; ++i){
Term *t = &lex->terms[i];
sends += t->incoming_len;
if(t->body_len < 1)
stubs++;
if(t->incoming_len < 1)
orphans++;
if(t->outgoing_len < 1)
deadends++;
}
if(gmi){
fprintf(f, "\n## Indexed Documents\n"
"This wiki hosts %d journal logs recorded on %d lexicon terms, "
"connected by %d inbound links. It is a living document in which %d stubs, "
"%d orphans and %d deadends still remain.\n",
jou->len, lex->len, sends, stubs, orphans, deadends);
fputs("An orphan is a term that is not linked from another term of the lexicon. "
"A deadend is a term that do not link to another term in the lexicon.\n", f);
}else{
fputs("<h2>Indexed Documents</h2>"
"<figure><aside class='note'><p>An <i>orphan</i> is a term that is not linked "
"from another term of the lexicon. A <i>deadend</i> is a term that do not link to "
"another term in the lexicon.</p></aside></figure>", f);
fprintf(f, "<p>This wiki hosts %d journal logs recorded on %d lexicon terms, connected "
"by %d inbound links. It is a living document in which %d stubs, "
"%d orphans and %d deadends still remain.</p>",
jou->len, lex->len, sends, stubs, orphans, deadends);
fputs("<ul class='toc'>", f);
fpindexsub(f, &lex->terms[0], 0);
fputs("</ul>", f);
}
}
void
fpgmi(FILE *f, Glossary *glo, Lexicon *lex, Term *t, Journal *jou)
{
Term *alias = NULL;
if(t->type && scmp(t->type, "alias"))
alias = findterm(lex, t->host);
if(!scmp(t->name, "index"))
fputs("=>\tindex.gmi\t🏠 Home\n", f);
if(!scmp(t->parent->name, "index"))
fprintf(f, "=>\t%s.gmi\t↩️ Back to %s\n\n",
alias ? alias->filename : t->parent->filename,
alias ? alias->name : t->parent->name);
fpbanner(f, jou, alias ? alias : t, 1, 1);
if(!scmp(t->name, "index"))
fpgmiheader(f, alias ? alias : t);
else
fputs("# roy niang’s Geminispace\n", f);
fpbody(f, glo, lex, alias ? alias : t, 1);
if(t->type && scmp(t->type, "album")){
fputs("\n", f);
fpalbum(f, jou, t, 1);
}else if(t->type && scmp(t->type, "pict_portal")){
fputs("\n", f);
fpgmiportal(f, alias ? alias : t, jou);
}
if(scmp(t->name, "index")){
fphome(f, jou, 1);
fpindex(f, lex, jou, 1);
}else if(scmp(t->name, "journal"))
fpgmijournal(f, jou);
else if(scmp(t->name, "tracker"))
fptracker(f, jou, 1);
else if(scmp(t->name, "calendar"))
fpcalendar(f, jou, 1);
else if(scmp(t->name, "now"))
fpnow(f, lex, jou, 1);
if(!scmp(t->name, "index"))
fpgminav(f, alias ? alias : t);
if((t->type && scmp(t->type, "alias")) || (t->link.len >= 1) ||
(t->incoming_len >= 1) || (t->logs_len >= 2))
fputs("\n\n## Metadata\n", f);
if(t->type && scmp(t->type, "alias"))
fprintf(f, "\n=>\t%s.gmi\tRedirected to %s, from %s.\n",
alias->filename, alias->name, t->name);
fplinks(f, alias ? alias : t, 1);
fpincoming(f, alias ? alias : t, 1);
fphoraire(f, jou, alias ? alias : t, 1);
if(scmp(t->name, "index")){
fputs("\nThe content of this site is CC-BY-SA, and the code is MIT unless credit is given.", f);
}else if(!scmp(t->name, "index") && (!t->relm || !scmp(t->relm, "solo"))){
fputs("\n", f);
if(t->body_len != 0)
fprintf(f, "=>\thttps://" DOMAIN "/%s.html\tWeb version\n", t->filename);
fputs("=>\tmailto:roy@royniang.com\troy@royniang.com\n", f);
}
fclose(f);
}
void
fphtml(FILE *f, Glossary *glo, Lexicon *lex, Term *t, Journal *jou)
{
Term *alias = NULL;
if(t->type && scmp(t->type, "alias"))
alias = findterm(lex, t->host);
fprintf(f, "<!DOCTYPE html><html lang='en'><head><meta charset='utf-8'>"
"<meta name='author' content='roy niang'>"
"<meta name='description' content='%s'/>"
"<meta name='twitter:description' content='%s'>"
"<meta name='license' content='name=BY-NC-SA(4.0), "
"url=https://creativecommons.org/licenses/by-nc-sa/4.0/'/>",
t->bref, t->bref);
fputs("<meta name='thumbnail' content='https://" DOMAIN "/media/services/thumbnail.jpg' />"
"<meta property='og:image' content='https://" DOMAIN "/media/services/thumbnail.jpg' />"
"<meta name='twitter:card' content='summary_large_image'>"
"<meta property='twitter:image' content='https://" DOMAIN "/media/services/thumbnail.jpg' />"
"<meta name='viewport' content='width=device-width, initial-scale=1'/>", f);
fputs("<link rel='alternate' type='application/rss+xml' title='RSS Feed' href='rss.xml' />"
"<link rel='stylesheet' type='text/css' href='links/main.css'>"
"<link rel='shortcut icon' type='image/png' href='media/services/icon.png'>"
"<link rel='webmention' href='https://webmention.io/" DOMAIN "/webmention' />"
"<link rel='pingback' href='https://webmention.io/" DOMAIN "/xmlrpc' />", f);
if(scmp(t->name, "index")){
fputs("<title>" NAME " — " DOMAIN "</title>"
"<meta property='og:title' content='" DOMAIN "' />"
"<meta property='twitter:title' content='" DOMAIN "' />"
"</head><body>", f);
}else{
fprintf(f, "<title>%s — " DOMAIN "</title>"
"<meta property='og:title' content='%s — " DOMAIN "' />"
"<meta property='twitter:title' content='%s — " DOMAIN "' />"
"</head><body>",
alias ? alias->titl : t->titl,
alias ? alias->titl : t->titl,
alias ? alias->titl : t->titl);
}
if(t->relm && scmp(t->relm, "solo"))
fputs("<div id='spacer'></div><main>", f);
else{
fputs("<header><div class='h-card'><a href='index.html' class='logo'>", f);
fplogo(f, t);
fputs("</a>", f);
fputs("<p class='menu'><a class='p-name u-url' "
"href='https://" DOMAIN "'>roy niang</a>’s wiki</p><ul class='menu'>"
"<li><a class='u-email' href='mailto:roy@royniang.com'>email</a></li>"
"<li><a rel='me' href='https://merveilles.town/@royniang'>Merveilles</a></li>"
"<li><a rel='me' href='https://ukese.me/@royo'>UKESEME</a></li>"
"<li><a rel='me' href='https://twitter.com/royniang'>Twitter</a></li>"
"<li><a href='rss.xml'>RSS</a></li>"
"<li><a href='http://webring.xxiivv.com/'>Webring</a></li></ul></div>", f);
fpnav(f, alias ? alias : t);
fputs("</header><div id='spacer'></div><main>", f);
}
fpbanner(f, jou, alias ? alias : t, 1, 0);
fpbody(f, glo, lex, alias ? alias : t, 0);
if(t->type){
if(scmp(t->type, "pict_portal"))
fpportal(f, glo, lex, jou, alias ? alias : t, 1, 0, 0);
else if(scmp(t->type, "text_portal"))
fpportal(f, glo, lex, jou, alias ? alias : t, 0, 1, 0);
else if(scmp(t->type, "card_portal"))
fpportal(f, glo, lex, jou, alias ? alias : t, 0, 0, 1);
else if(scmp(t->type, "toc_portal"))
fpportal(f, glo, lex, jou, alias ? alias : t, 0, 1, 1);
else if(scmp(t->type, "album"))
fpalbum(f, jou, alias ? alias : t, 0);
else if(scmp(t->type, "alias") || scmp(t->type, "log"))
;
else if(!scmp(t->type, "hidden"))
error("Unknown template", t->type);
}
if(scmp(t->name, "now"))
fpnow(f, lex, jou, 0);
else if(scmp(t->name, "index")){
fphome(f, jou, 0);
fpindex(f, lex, jou, 0);
}else if(scmp(t->name, "calendar"))
fpcalendar(f, jou, 0);
else if(scmp(t->name, "tracker"))
fptracker(f, jou, 0);
else if(scmp(t->name, "journal"))
fpjournal(f, jou);
else if(scmp(t->name, "logs"))
fplogs(f, t);
if(!scmp(statusterm(t), "\0")){
if(scmp(statusterm(t), "alias"))
fprintf(f, "<figure><aside class='disclaimer'><p>Redirected to "
"<a href='%s.html'>%s</a>, from <b>%s</b>.</p></aside></figure>",
alias->filename, alias->name, t->name);
else if(scmp(statusterm(t), "log") || scmp(statusterm(t), "article") || scmp(t->name, "index"))
;
else{
fputs("<figure><aside class='warning'><p>This document is ",f);
if(scmp(statusterm(t), "orphan"))
fputs("an", f);
else
fputs("a", f);
fprintf(f, " <b>%s</b>.", statusterm(t));
fputs("</p></aside></figure>", f);
}
}
if(t->link.len >= 1 || (t->incoming_len >= 1) ||
(t->logs_len >= 2) || (t->type && scmp(t->type, "alias"))){
fputs("<div id='metadata'>", f);
fplinks(f, alias ? alias : t, 0);
fpincoming(f, alias ? alias : t, 0);
fphoraire(f, jou, alias ? alias : t, 0);
fputs("</div>", f);
}
fputs("</main></body></html>", f);
fclose(f);
}
void
fptwtxt(FILE *f, Journal *jou)
{
int i;
for(i = 0; i < jou->len; ++i){
Log l = jou->logs[i];
if(!l.name)
continue;
fpRFC3339(f, arvelietime(EPOCH, l.date), 1, 0, 0);
fprintf(f, "\t%s - https://" DOMAIN "/%s.html\n", l.name, l.term->filename);
}
fclose(f);
}
void
fprss(FILE *f, Lexicon *lex, Journal *jou)
{
int i;
time_t now;
fputs("<?xml version='1.0' encoding='UTF-8' ?>\n"
"<rss version='2.0' xmlns:dc='http://purl.org/dc/elements/1.1/'>\n"
"<channel>\n"
"<title>" NAME " — " DOMAIN "</title>\n"
"<link>https://" DOMAIN "/journal.html</link>\n"
"<description>The Patience Database</description>\n"
"<lastBuildDate>", f);
fpRFC2822(f, time(&now), 0, 0);
fputs("</lastBuildDate>\n"
"<image>\n"
" <url>https://" DOMAIN "/media/services/rss.jpg</url>\n"
" <title>The Patience Database</title>\n"
" <link>https://" DOMAIN "/journal.html</link>\n"
"</image>\n", f);
for(i = 0; i < lex->len; ++i){
Term *t = &lex->terms[i];
if(t->date == NULL)
continue;
fputs("<item>\n", f);
fprintf(f, " <title>%s</title>\n"
" <link>https://" DOMAIN "/%s.html</link>\n"
" <guid isPermaLink='false'>%s</guid>\n",
t->titl, t->filename, t->filename);
fputs(" <pubDate>", f);
if(t->time)
fpRFC2822(f, arvelietime(EPOCH, t->date), t->time, 0);
else
fpRFC2822(f, arvelietime(EPOCH, t->date), 0, 0);
fputs("</pubDate>\n"
" <dc:creator><![CDATA[roy niang]]></dc:creator>\n"
" <description>\n"
"<![CDATA[", f);
fpinclude(f, t->filename, 0, 0, 0);
fputs("]]>\n"
" </description>\n"
"</item>\n", f);
}
for(i = 0; i < jou->len; ++i){
Log l = jou->logs[i];
if(l.pict == 0)
continue;
fputs("<item>\n", f);
fprintf(f, " <title>%s</title>\n"
" <link>https://" DOMAIN "/%s.html</link>\n"
" <guid isPermaLink='false'>%d</guid>\n",
l.name, l.term->filename, l.pict);
fputs(" <pubDate>", f);
fpRFC2822(f, arvelietime(EPOCH, l.date), 0, 0);
fputs("</pubDate>\n"
" <dc:creator><![CDATA[roy niang]]></dc:creator>\n"
" <description>\n"
"<![CDATA[", f);
fprintf(f, "<img src='https://" DOMAIN "/media/diary/%d.%s'/>\n"
"<p>%s — %s</p>",
l.pict, l.ext, l.term->titl, l.term->bref ? l.term->bref : "");
fputs("]]>\n"
" </description>\n"
"</item>\n", f);
}
fputs("</channel></rss>", f);
fclose(f);
}
void
fpgmiatom(FILE *f, Lexicon *lex, Journal *jou)
{
int i;
time_t now;
fputs("<?xml version='1.0' encoding='UTF-8'?>\n"
"<feed xmlns='http://www.w3.org/2005/Atom'>\n"
" <title>" NAME " — " DOMAIN "</title>\n"
" <link href='gemini://" DOMAIN "/atom.xml' rel='self'/>\n"
" <link href='gemini://" DOMAIN "' rel='alternate'/>\n"
" <updated>", f);
fpRFC3339(f, time(&now), 1, 0, 0);
fputs("</updated>\n"
" <author>\n"
" <name>roy niang</name>\n"
" <email>roy@royniang.com</email>\n"
" </author>\n"
" <id>gemini://" DOMAIN "</id>\n", f);
for(i = 0; i < lex->len; ++i){
Term t = lex->terms[i];
if(t.date == NULL)
continue;
fputs("<entry>\n", f);
fprintf(f, " <title>%s — %s</title>\n"
" <link rel='alternate' href='gemini://" DOMAIN "/%s.gmi'/>\n"
" <id>gemini://" DOMAIN "/%s.gmi</id>\n",
t.titl, t.bref, t.filename, t.filename);
fputs(" <updated>", f);
fpRFC3339(f, arvelietime(EPOCH, t.date), 1, t.time, 0);
fputs("</updated>\n"
"</entry>\n", f);
}
for(i = 0; i < jou->len; ++i){
Log l = jou->logs[i];
if(l.pict == 0)
continue;
fputs("<entry>\n", f);
fprintf(f, " <title>%s</title>\n"
" <link href='gemini://" DOMAIN "/%s.gmi' />\n"
" <id>gemini://" DOMAIN "/%s</id>\n",
l.name, l.term->filename, l.term->filename);
fputs(" <updated>", f);
fpRFC3339(f, arvelietime(EPOCH, l.date), 1, 0, 0);
fputs("</updated>\n"
"</entry>\n", f);
}
fputs("</feed>\n", f);
fclose(f);
}
#pragma mark - Parse
int
parse_glossary(FILE *fp, Block *block, Glossary *glo)
{
int len, depth, count = 0, split = 0;
char line[512], buf[1024];
List *l = &glo->lists[glo->len];
while(fgets(line, 512, fp)){
depth = spad(line, '\t');
len = slen(strm(line));
count++;
if(len < 4 || line[0] == ';')
continue;
if(glo->len >= GLOMEM)
return errorid("Increase memory", "glossary", glo->len);
if(len > 400)
return errorid("Line is too long", line, len);
if(depth == 0)
l = makelist(&glo->lists[glo->len++], push(block, sstr(line, buf, 0, len)));
else if(depth == 1){
if(l->len >= 64)
errorid("Reached list item limit", l->name, l->len);
split = scin(line, ':');
if(split < 0)
l->vals[l->len] = push(block, sstr(line, buf, 1, len + 1));
else{
l->keys[l->len] = push(block, sstr(line, buf, 1, split - 2));
l->vals[l->len] = push(block, sstr(line, buf, split + 2, len - split));
}
l->len++;
}
}
printf(" (%d) · ", count);
return 1;
}
int
parse_lexicon(FILE *fp, Block *block, Lexicon *lex)
{
int key_len, val_len, len, count = 0, catch_body = 0, catch_gmni = 0, catch_link = 0;
char line[1024], buf[1024];
Term *t = &lex->terms[lex->len];
while(fgets(line, 1024, fp)){
int depth = spad(line, '\t');
strm(line);
len = slen(line);
count++;
if(len < 3 || line[0] == ';')
continue;
if(lex->len >= LEXMEM)
return errorid("Increase memory", "Lexicon", lex->len);
if(len > 750)
return errorid("Line is too long", line, len);
if(depth == 0){
t = maketerm(&lex->terms[lex->len++],
push(block, sstr(line, buf, 0, len)));
if(!sian(line))
return error("Lexicon key is not alphanum", line);
t->filename = push(block, scsw(stlc(sstr(line, buf, 0, len)), ' ', '_'));
}else if(depth == 1 && len > 2){
if(ssin(line, "HOST : ") >= 0)
t->host = push(block, sstr(line, buf, 8, len - 8));
if(ssin(line, "BREF : ") >= 0)
t->bref = push(block, sstr(line, buf, 8, len - 8));
if(ssin(line, "TITL : ") >= 0)
t->titl = push(block, sstr(line, buf, 8, len - 8));
if(ssin(line, "TYPE : ") >= 0)
t->type = push(block, sstr(line, buf, 8, len - 8));
if(ssin(line, "RELM : ") >= 0)
t->relm = push(block, sstr(line, buf, 8, len - 8));
if(ssin(line, "DATE : ") >= 0)
t->date = push(block, sstr(line, buf, 8, len - 8));
if(ssin(line, "UPDT : ") >= 0)
t->updt = push(block, sstr(line, buf, 8, len - 8));
if(ssin(line, "TIME : ") >= 0)
t->time = push(block, sstr(line, buf, 8, len - 8));
if(ssin(line, "UTIM : ") >= 0)
t->utim = push(block, sstr(line, buf, 8, len - 8));
catch_body = ssin(line, "BODY") >= 0;
catch_gmni = ssin(line, "GMNI") >= 0;
catch_link = ssin(line, "LINK") >= 0;
}else if(depth == 2 && len > 3){
/* Body */
if(catch_body)
t->body[t->body_len++] = push(block, sstr(line, buf, 2, len - 2));
/* Gemini */
else if(catch_gmni){
t->gmni[t->gmni_len++] = push(block, sstr(line, buf, 2, len - 2));
/* Link */
}else if(catch_link){
key_len = scin(line, ':') - 3;
t->link.keys[t->link.len] = push(block, sstr(line, buf, 2, key_len));
val_len = len - key_len - 3;
t->link.vals[t->link.len++] = push(block, sstr(line, buf, key_len + 5, val_len));
}else
return errorid("Invalid line", line, count);
}else
return errorid("Invalid line", line, count);
}
printf(" (%d) · ", count);
return 1;
}
int
parse_journal(FILE *fp, Block *block, Lexicon *lex, Journal *jou)
{
int len, count = 0;
char line[256], buf[256];
Log *l = &jou->logs[jou->len];
while(fgets(line, 256, fp)){
len = slen(strm(line));
count++;
if(len < 14 || line[0] == ';')
continue;
if(jou->len >= HORMEM)
return errorid("Increase memory", "Horaire", jou->len);
if(len > 90)
return errorid("Log is too long", line, len);
l = makelog(&jou->logs[jou->len++], push(block, sstr(line, buf, 0, 5)));
l->rune = line[6];
l->code = sint(line + 7, 3);
l->term = findterm(lex, strm(sstr(line, buf, 11, 21)));
if(!l->term)
return error("Unknown log term", line);
if(len >= 35)
l->pict = sint(line + 32, 3);
if(len >= 39)
l->ext = push(block, strm(sstr(line, buf, 36, 3)));
if(len >= 42)
l->name = push(block, strm(sstr(line, buf, 40, 90)));
}
printf(" (%d) ", count);
return 1;
}
int
parse(Block *block, Glossary *glo, Lexicon *lex, Journal *jou)
{
FILE *fglo = fopen("database/glossary.ndtl", "r");
FILE *flex = fopen("database/lexicon.ndtl", "r");
FILE *fhor = fopen("database/journal.tbtl", "r");
printf("Parse\t|\t");
printf("glossary");
if(!fglo || !parse_glossary(fglo, block, glo)){
fclose(fglo);
return error("Parsing", "Glossary");
}
printf("lexicon");
if(!flex || !parse_lexicon(flex, block, lex)){
fclose(flex);
return error("Parsing", "Lexicon");
}
printf("journal");
if(!fhor || !parse_journal(fhor, block, lex, jou)){
fclose(fhor);
return error("Parsing", "Horaire");
}
fclose(fglo);
fclose(flex);
fclose(fhor);
return 1;
}
int
link(Block *block, Glossary *glo, Lexicon *lex, Journal *jou)
{
int i, j;
char buf[7];
printf("Link \t|\t");
printf("glossary (%d) · ", glo->len);
printf("lexicon (%d) · ", lex->len);
for(i = 0; i < lex->len; ++i){
Term *t = &lex->terms[i];
for(j = 0; j < t->body_len; ++j)
fptemplate(NULL, glo, lex, t, t->body[j], 0);
t->parent = findterm(lex, t->host);
if(!t->parent)
return error("Missing parent ", t->host);
if(!t->bref && !t->type)
return error("Missing bref ", t->name);
if(!t->titl && !scmp(t->type, "alias"))
return error("Missing title ", t->name);
t->parent->children[t->parent->children_len++] = t;
}
printf("journal (%d) ", jou->len);
for(i = 0; i < jou->len; ++i){
Log *l = &jou->logs[i];
l->term->logs_len++;
l->term->ch += (l->code / 10) % 10;
l->term->fh += l->code % 10;
if(l->rune == '+'){
l->term->events_len++;
l->term->parent->events_len++;
}
if(!l->term->date_last)
l->term->date_last = push(block, scpy(l->date, buf, 6));
if(l->code < 1)
return error("Empty code", l->date);
l->term->date_from = push(block, scpy(l->date, buf, 6));
}
return 1;
}
int
build(Glossary *glo, Lexicon *lex, Journal *jou)
{
FILE *f;
int i;
printf("Build \t|\t");
printf("%d pages ", lex->len);
for(i = 0; i < lex->len; ++i){
if(lex->terms[i].body_len == 0 && lex->terms[i].gmni_len != 0)
continue;
else{
f = getfile("build/web/", lex->terms[i].filename, ".html", "w");
if(!f)
return error("Could not open file", lex->terms[i].name);
fphtml(f, glo, lex, &lex->terms[i], jou);
}
}
for(i = 0; i < lex->len; ++i){
f = getfile("build/gmi/", lex->terms[i].filename, ".gmi", "w");
if(!f)
return error("Could not open file", lex->terms[i].name);
fpgmi(f, glo, lex, &lex->terms[i], jou);
}
printf("3 feeds ");
f = fopen("build/web/rss.xml", "w");
if(!f)
return error("Could not open file", "rss.xml");
fprss(f, lex, jou);
f = fopen("build/web/tw.txt", "w");
if(!f)
return error("Could not open file", "tw.txt");
fptwtxt(f, jou);
f = fopen("build/gmi/atom.xml", "w");
if(!f)
return error("Could not open file", "atom.xml");
fpgmiatom(f, lex, jou);
return 1;
}
void
check(Glossary *glo, Journal *jou)
{
int i, j, found = 0;
printf("\nCheck:\n");
/* Find unlinked lists */
for(i = 0; i < glo->len; ++i){
List *l = &glo->lists[i];
if(l->routes < 1)
printf("\t- Warning: Unused list \"%s\"\n", l->name);
}
/* Find next available diary id */
for(i = 1; i < 999; ++i){
found = 0;
for(j = 0; j < jou->len; j++)
if(jou->logs[j].pict == i || found)
found = 1;
if(!found){
printf("\t- Available: #%d\n", i);
break;
}
}
}
Block block;
Glossary all_lists;
Lexicon all_terms;
Journal all_logs;
int
main(void)
{
parvelie(EPOCH);
puts("");
if(!parse(&block, &all_lists, &all_terms, &all_logs))
return error("Failure", "Parsing");
printf("\n");
if(!link(&block, &all_lists, &all_terms, &all_logs))
return error("Failure", "Linking");
printf("\n");
if(!build(&all_lists, &all_terms, &all_logs))
return error("Failure", "Building");
printf("\n");
check(&all_lists, &all_logs);
printf("\n");
printf("%d/%d characters in memory\n", block.len, STRMEM);
return 0;
}