486 lines
12 KiB
C
486 lines
12 KiB
C
#include <dirent.h>
|
|
#include <stdio.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
#include <time.h>
|
|
/*
|
|
Copyright (c) 2021 Derek Stevens
|
|
Copyright (c) 2021 Devine Lu Linvega
|
|
|
|
Permission to use, copy, modify, and distribute this software for any
|
|
purpose with or without fee is hereby granted, provided that the above
|
|
copyright notice and this permission notice appear in all copies.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
WITH REGARD TO THIS SOFTWARE.
|
|
*/
|
|
|
|
/* clang-format off */
|
|
|
|
#define NAME "nilFM"
|
|
#define DOMAIN "https://nilfm.cc"
|
|
#define ABOUT "Derek Stevens <"\
|
|
"<a href='mailto://nilix@nilfm.cc'>nilix@nilfm.cc</a>><br/>"\
|
|
"artist, programmer, philosopher<br/><br/>"\
|
|
"verify my signature: <a href='/serv/signingKey.pub'>signing public key</a><br/>"\
|
|
"send me an encrypted message: <a href='/serv/encryptionKey.pub'>encryption public key</a>"
|
|
#define SITEROOT "../www/"
|
|
#define MAINCSS "/new.css"
|
|
#define FRONTCSS "/front.css"
|
|
#define LICENSE ""
|
|
#define SOURCE ""
|
|
|
|
/* clang-format on */
|
|
|
|
struct dirent* dir;
|
|
|
|
typedef struct Lexicon {
|
|
int len, refs[512];
|
|
char files[512][64];
|
|
} Lexicon;
|
|
|
|
/* clang-format off */
|
|
|
|
char clca(char c) { return c >= 'A' && c <= 'Z' ? c + ('a' - 'A') : c; } /* char to lowercase */
|
|
char cuca(char c) { return c >= 'a' && c <= 'z' ? c - ('a' - 'A') : c; } /* char to uppercase */
|
|
int slen(char *s) { int i = 0; while(s[i] && s[++i]) { ; } return i; } /* string length */
|
|
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) { return st__(s, cuca); } /* string to uppercase */
|
|
char *stlc(char *s) { return st__(s, clca); } /* string to lowercase */
|
|
char *scpy(char *src, char *dst, int len) { int i = 0; while((dst[i] = src[i]) && i < len - 2) i++; dst[i + 1] = '\0'; return dst; } /* string copy */
|
|
int scmp(char *a, char *b) { int i = 0; while(a[i] == b[i]) if(!a[i++]) return 1; return 0; } /* string compare */
|
|
char *scsw(char *s, char a, char b) { int i = 0; char c; while((c = s[i])) s[i++] = c == a ? b : c; return s; } /* string char swap */
|
|
char *scat(char *dst, const char *src) { char *ptr = dst + slen(dst); while(*src) *ptr++ = *src++; *ptr = '\0'; return dst; } /* string cat */
|
|
int ssin(char *s, char *ss) { 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; } /* string substring index */
|
|
char *ccat(char *dst, char c) { int len = slen(dst); dst[len] = c; dst[len + 1] = '\0'; return dst; }
|
|
int clin(char *str, char c) {int i = -1; int j = 0; while(str[j] != '\0'){if(str[j] == c) i = j;j++;}return i;} /* find last occurence of character in string */
|
|
/* clang-format on */
|
|
|
|
int fpinject(FILE* f, Lexicon* l, char* filepath);
|
|
|
|
int error(char* msg, char* val) {
|
|
printf("Error: %s(%s)\n", msg, val);
|
|
return 0;
|
|
}
|
|
|
|
int ismetanav(char* name) { return scmp(name, "meta.nav"); }
|
|
|
|
int findf(Lexicon* l, char* f) {
|
|
int i;
|
|
char filename[64];
|
|
scat(scsw(stlc(scpy(f, filename, 64)), ' ', '_'), ".htm");
|
|
for (i = 0; i < l->len; ++i)
|
|
if (scmp(l->files[i], filename))
|
|
return i;
|
|
return -1;
|
|
}
|
|
|
|
int gettwtxt(FILE* f) {
|
|
char buf[1024] = {0};
|
|
char line[256];
|
|
char datebuf[16] = {0};
|
|
char msgbuf[240] = {0};
|
|
char *l, *d, *m;
|
|
FILE* twtxt = fopen(SITEROOT "twtxt.txt", "r");
|
|
|
|
scat(buf, "<div id='twtxtFeed'><h2>recent activity:</h2>");
|
|
if (!twtxt) {
|
|
error("Get twtxt feed", "no twtxt.txt");
|
|
return 0;
|
|
} else {
|
|
int i = 0;
|
|
while (fgets(line, 256, twtxt) && i < 3) {
|
|
l = line;
|
|
while (*l != 'T') {
|
|
ccat(datebuf, *l++);
|
|
}
|
|
while (*l != '\t') {
|
|
l++;
|
|
}
|
|
scat(msgbuf, l);
|
|
scat(buf, datebuf);
|
|
scat(buf, ": ");
|
|
scat(buf, msgbuf);
|
|
scat(buf, "<br/>");
|
|
d = datebuf;
|
|
while (*d) {
|
|
*d++ = 0;
|
|
}
|
|
m = msgbuf;
|
|
while (*m) {
|
|
*m++ = 0;
|
|
}
|
|
i++;
|
|
}
|
|
scat(buf, "<a href='/twtxt.txt'>see all</a>");
|
|
scat(buf, "</div>");
|
|
fputs(buf, f);
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
void fpedited(FILE* f, char* path) {
|
|
struct stat attr;
|
|
stat(path, &attr);
|
|
fprintf(f, "Edited on %s<br/>", ctime(&attr.st_mtime));
|
|
}
|
|
|
|
int fpportal(FILE* f, Lexicon* l, char* s, int head) {
|
|
int target;
|
|
char srcpath[64], filename[64];
|
|
target = findf(l, s);
|
|
if (target < 0)
|
|
return error("Missing portal", s);
|
|
srcpath[0] = 0;
|
|
filename[0] = 0;
|
|
scat(scat(scat(srcpath, "inc/"), scpy(s, filename, 64)), ".htm");
|
|
if (head)
|
|
fprintf(
|
|
f,
|
|
"<h3 id='%s'><a href='%s.html'>%s</a></h3>",
|
|
scsw(filename, ' ', '_'),
|
|
filename,
|
|
s);
|
|
fpinject(f, l, srcpath);
|
|
l->refs[target]++;
|
|
return 1;
|
|
}
|
|
|
|
int fphref(FILE* f, char* s) {
|
|
char href[1024] = {0};
|
|
char txt[1024] = {0};
|
|
|
|
char* c = s;
|
|
int i = 0;
|
|
while (i < 2) {
|
|
if (*c == '|' || !(*c)) {
|
|
i++;
|
|
c++;
|
|
continue;
|
|
}
|
|
switch (i) {
|
|
case 0:
|
|
ccat(href, *c++);
|
|
break;
|
|
case 1:
|
|
ccat(txt, *c++);
|
|
break;
|
|
}
|
|
}
|
|
fprintf(f, "<a href='%s'>%s</a>", href, txt);
|
|
return 1;
|
|
}
|
|
|
|
char* thumbtrans(char* s, char* thumb) {
|
|
int i, j;
|
|
char* ss;
|
|
char* fnss;
|
|
char fn[1024];
|
|
|
|
scpy(s, thumb, slen(s));
|
|
|
|
i = clin(thumb, '/');
|
|
ss = thumb + i + 1;
|
|
|
|
scpy(ss, fn, slen(thumb) - i + 1);
|
|
scpy(".thumb/", ss, 8);
|
|
|
|
j = clin(fn, '.');
|
|
fnss = fn + j;
|
|
scpy(".png", fnss, 5);
|
|
scat(thumb, fn);
|
|
return thumb;
|
|
}
|
|
|
|
int fpimg(FILE* f, char* s) {
|
|
char id[1024] = {0};
|
|
char src[1024] = {0};
|
|
char alt[1024] = {0};
|
|
char thumb[1024] = {0};
|
|
|
|
char* c = s;
|
|
int i = 0;
|
|
while (i < 3) {
|
|
if (*c == '|' || !(*c)) {
|
|
i++;
|
|
c++;
|
|
continue;
|
|
}
|
|
switch (i) {
|
|
case 0:
|
|
ccat(id, *c++);
|
|
break;
|
|
case 1:
|
|
ccat(src, *c++);
|
|
break;
|
|
case 2:
|
|
ccat(alt, *c++);
|
|
break;
|
|
}
|
|
}
|
|
|
|
thumbtrans(src, thumb);
|
|
fprintf(f, "<a id='%s' href='%s'>\n", id, src);
|
|
fprintf(f, "<img class='image' src='%s' alt='%s'/>\n", thumb, alt);
|
|
fputs("</a>", f);
|
|
return 1;
|
|
}
|
|
|
|
int fphimg(FILE* f, char* s) {
|
|
char id[1024] = {0};
|
|
char href[1024] = {0};
|
|
char src[1024] = {0};
|
|
char alt[1024] = {0};
|
|
char thumb[1024] = {0};
|
|
|
|
char* c = s;
|
|
int i = 0;
|
|
while (i < 4) {
|
|
if (*c == '|' || !(*c)) {
|
|
i++;
|
|
c++;
|
|
continue;
|
|
}
|
|
switch (i) {
|
|
case 0:
|
|
ccat(id, *c++);
|
|
break;
|
|
case 1:
|
|
ccat(href, *c++);
|
|
break;
|
|
case 2:
|
|
ccat(src, *c++);
|
|
break;
|
|
case 3:
|
|
ccat(alt, *c++);
|
|
break;
|
|
}
|
|
}
|
|
|
|
thumbtrans(src, thumb);
|
|
|
|
fprintf(f, "<a id='%s' href='%s'>\n", id, href);
|
|
fprintf(f, "<img class='image' src='%s' alt='%s'/>\n", thumb, alt);
|
|
fputs("</a>", f);
|
|
return 1;
|
|
}
|
|
|
|
int fpaudio(FILE* f, char* s) {
|
|
fputs("<audio class='player' preload='metadata' controls>", f);
|
|
fprintf(f, "<source src='%s' type='audio/mpeg'>[HTML5 audio]", s);
|
|
fputs("</audio>", f);
|
|
return 1;
|
|
}
|
|
|
|
int fptemplate(FILE* f, Lexicon* l, char* s) {
|
|
int target;
|
|
if (s[0] == '/')
|
|
return fpportal(f, l, s + 1, 1);
|
|
if (s[0] == '*')
|
|
return fphref(f, s + 1);
|
|
if (s[0] == ':')
|
|
return fpimg(f, s + 1);
|
|
if (s[0] == '?')
|
|
return fphimg(f, s + 1);
|
|
if (s[0] == '_')
|
|
return fpaudio(f, s + 1);
|
|
target = findf(l, s);
|
|
if (target < 0)
|
|
return error("Missing link", s);
|
|
fprintf(f, "<a href='%s.html' class='local'>", scsw(stlc(s), ' ', '_'));
|
|
fprintf(f, "%s</a>", scsw(stlc(s), '_', ' '));
|
|
l->refs[target]++;
|
|
return 1;
|
|
}
|
|
|
|
int fpinject(FILE* f, Lexicon* l, char* filepath) {
|
|
FILE* inc;
|
|
char c, s[1024];
|
|
unsigned char t = 0;
|
|
scsw(filepath, ' ', '_');
|
|
if (!(inc = fopen(filepath, "r")))
|
|
return error("Missing include", filepath);
|
|
s[0] = 0;
|
|
while ((c = fgetc(inc)) != EOF) {
|
|
if (c == '}') {
|
|
t = 0;
|
|
if (!fptemplate(f, l, s))
|
|
return 0;
|
|
continue;
|
|
}
|
|
if (c == '{') {
|
|
s[0] = 0;
|
|
t = 1;
|
|
continue;
|
|
}
|
|
if (slen(s) > 1023)
|
|
return error("Templating error", filepath);
|
|
if (t)
|
|
ccat(s, c);
|
|
else
|
|
fprintf(f, "%c", c);
|
|
}
|
|
fclose(inc);
|
|
return 1;
|
|
}
|
|
|
|
int fpfooter(FILE* f, char* name, char* path) {
|
|
if (!f || !name || !path)
|
|
return 0;
|
|
fputs("<footer>", f);
|
|
if (!ismetanav(name)) {
|
|
fpedited(f, path);
|
|
fputs("contact: <a href='mailto://nilix@nilfm.cc'>nilix@nilfm.cc</a> ", f);
|
|
fputs("(<a href='keys.html'>keys</a>)<br/>", f);
|
|
}
|
|
fputs(
|
|
"<a href='" DOMAIN "/git/'><img width='24' height='24' "
|
|
"src='/img/git.svg' alt='visit the nilFM hack lab'/></a> ",
|
|
f);
|
|
fputs(
|
|
"<a rel=\"me\" href=\"https://fosstodon.org/@nilix\"><img width='24' "
|
|
"height='24' "
|
|
"src='/img/mastodon.svg' alt='visit me on Mastodon'/></a> ",
|
|
f);
|
|
fputs(
|
|
"<a href='https://vimeo.com/rexnillith'><img width='24' height='24' "
|
|
"src='/img/vimeo.svg' alt='visit me on Vimeo'/></a> ",
|
|
f);
|
|
fputs(
|
|
"<a href='https://webring.xxiivv.com'><img width='24' height='24' "
|
|
"src='/img/webring.svg' alt='visit the Webring'/></a><br/>",
|
|
f);
|
|
/*fputs(
|
|
"<a href='https://lieu.cblgh.org'><img width='24' height='24' "
|
|
"src='/img/lieu.svg' alt='search the webring'/></a><br/>",
|
|
f);*/
|
|
fputs(
|
|
"<a rel='license' "
|
|
"href='https://creativecommons.org/licenses/by-nc/4.0/"
|
|
"legalcode.txt'>CC-BY-NC 4</a>",
|
|
f);
|
|
fputs("</footer>", f);
|
|
return 1;
|
|
}
|
|
|
|
FILE* build(FILE* f, Lexicon* l, char* name, char* srcpath) {
|
|
if (!f)
|
|
return f;
|
|
/* begin */
|
|
fputs("<!DOCTYPE html><html lang='en'>", f);
|
|
fputs("<head>", f);
|
|
fprintf(
|
|
f,
|
|
"<meta charset='utf-8'>"
|
|
"<meta name='description' content='%s'/>"
|
|
"<meta name='viewport' content='width=device-width,initial-scale=1'>",
|
|
"lair of drkste aka nilix: programmer, artist, philosopher");
|
|
if (ismetanav(name)) {
|
|
fputs("<link rel='stylesheet' type='text/css' href='" FRONTCSS "'>", f);
|
|
} else {
|
|
fputs("<link rel='stylesheet' type='text/css' href='" MAINCSS "'>", f);
|
|
}
|
|
fprintf(
|
|
f,
|
|
"<link rel='shortcut icon' href='/favicon.ico'>"
|
|
"<title>" NAME " — %s</title>",
|
|
ismetanav(name) ? "home" : name);
|
|
fputs("</head>", f);
|
|
fputs("<body>", f);
|
|
/* header */
|
|
fputs("<header>", f);
|
|
if (ismetanav(name)) {
|
|
fputs("<h1>nilFM</h1>", f);
|
|
} else {
|
|
fputs("<h1><a href='/'>nilFM</a></h1>", f);
|
|
}
|
|
|
|
fputs("</header>", f);
|
|
/* about (main page only) */
|
|
if (ismetanav(name)) {
|
|
fputs("<div id='about'>" ABOUT "</div>", f);
|
|
}
|
|
/* nav */
|
|
fputs("<nav>", f);
|
|
if (!fpportal(f, l, "meta.nav", 0))
|
|
printf(">>> Building failed: %s\n", name);
|
|
fputs("</nav>", f);
|
|
/* main */
|
|
if (!ismetanav(name)) {
|
|
fputs("<main>\n\n", f);
|
|
fputs("<!-- Generated file, do not edit -->\n\n", f);
|
|
fprintf(f, "<h2>%s</h2>", name);
|
|
if (!fpinject(f, l, srcpath))
|
|
printf(">>> Building failed: %s\n", name);
|
|
fputs("\n\n</main>", f);
|
|
} else {
|
|
gettwtxt(f);
|
|
}
|
|
/* footer */
|
|
if (!fpfooter(f, name, srcpath)) {
|
|
printf(">>> Building failed: footer(%s)\n", name);
|
|
}
|
|
|
|
/* end */
|
|
fputs("</body></html>", f);
|
|
return f;
|
|
}
|
|
|
|
int generate(Lexicon* l) {
|
|
int i = 0;
|
|
char srcpath[64], dstpath[64], filename[64];
|
|
for (i = 0; i < l->len; ++i) {
|
|
srcpath[0] = 0;
|
|
dstpath[0] = 0;
|
|
filename[0] = 0;
|
|
/* src */
|
|
scpy(l->files[i], filename, ssin(l->files[i], ".htm") + 1);
|
|
scat(srcpath, "inc/");
|
|
scat(srcpath, filename);
|
|
scat(srcpath, ".htm");
|
|
/* dst */
|
|
if (scmp(filename, "meta.nav")) {
|
|
scat(dstpath, SITEROOT);
|
|
scat(dstpath, "index.html");
|
|
} else {
|
|
scat(dstpath, SITEROOT);
|
|
scat(dstpath, filename);
|
|
scat(dstpath, ".html");
|
|
}
|
|
fclose(build(fopen(dstpath, "w"), l, scsw(filename, '_', ' '), srcpath));
|
|
}
|
|
printf("Generated %d files\n", i);
|
|
return 1;
|
|
}
|
|
|
|
int index(Lexicon* l, DIR* d) {
|
|
while ((dir = readdir(d)))
|
|
if (ssin(dir->d_name, ".htm") > 0) {
|
|
l->refs[l->len] = 0;
|
|
scpy(dir->d_name, l->files[l->len++], 64);
|
|
}
|
|
closedir(d);
|
|
printf("Indexed %d terms\n", l->len);
|
|
return 1;
|
|
}
|
|
|
|
void inspect(Lexicon* l) {
|
|
int i;
|
|
for (i = 0; i < l->len; ++i)
|
|
if (!l->refs[i])
|
|
error("Orphaned", l->files[i]);
|
|
}
|
|
|
|
int main(void) {
|
|
Lexicon lex;
|
|
DIR* d;
|
|
lex.len = 0;
|
|
if (!(d = opendir("inc")))
|
|
return error("Open", "Missing inc/ folder. ");
|
|
if (!index(&lex, d))
|
|
return error("Indexing", "Failed");
|
|
if (!generate(&lex))
|
|
return error("Generating", "Failed");
|
|
inspect(&lex);
|
|
return 0;
|
|
}
|