2021-04-15 18:40:40 +00:00
|
|
|
#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://drkste@zoho.com'>drkste@zoho.com</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'>encrypted public key</a>"
|
|
|
|
#define SITEROOT "../www/"
|
|
|
|
#define MAINCSS "/new.css"
|
|
|
|
#define FRONTCSS "/front.css"
|
|
|
|
#define LICENSE ""
|
|
|
|
#define SOURCE ""
|
|
|
|
|
|
|
|
/* clang-format on */
|
|
|
|
|
2021-04-15 18:44:07 +00:00
|
|
|
struct dirent* dir;
|
2021-04-15 18:40:40 +00:00
|
|
|
|
|
|
|
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; }
|
2021-04-17 19:02:02 +00:00
|
|
|
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 */
|
2021-04-15 18:40:40 +00:00
|
|
|
/* clang-format on */
|
|
|
|
|
2021-04-15 18:44:07 +00:00
|
|
|
int fpinject(FILE* f, Lexicon* l, char* filepath);
|
2021-04-15 18:40:40 +00:00
|
|
|
|
2021-04-15 18:44:07 +00:00
|
|
|
int error(char* msg, char* val) {
|
2021-04-15 18:40:40 +00:00
|
|
|
printf("Error: %s(%s)\n", msg, val);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2021-04-15 18:44:07 +00:00
|
|
|
int ismetanav(char* name) { return scmp(name, "meta.nav"); }
|
2021-04-15 18:40:40 +00:00
|
|
|
|
2021-04-15 18:44:07 +00:00
|
|
|
int findf(Lexicon* l, char* f) {
|
2021-04-15 18:40:40 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2021-04-15 18:44:07 +00:00
|
|
|
void fpedited(FILE* f, char* path) {
|
2021-04-15 18:40:40 +00:00
|
|
|
struct stat attr;
|
|
|
|
stat(path, &attr);
|
|
|
|
fputs("<span style='float:right'>", f);
|
|
|
|
fprintf(f, "Edited on %s ", ctime(&attr.st_mtime));
|
|
|
|
fputs("</span>", f);
|
|
|
|
}
|
|
|
|
|
2021-04-15 18:44:07 +00:00
|
|
|
int fpportal(FILE* f, Lexicon* l, char* s, int head) {
|
2021-04-15 18:40:40 +00:00
|
|
|
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)
|
2021-04-15 18:44:07 +00:00
|
|
|
fprintf(
|
|
|
|
f,
|
2021-04-17 19:02:02 +00:00
|
|
|
"<h3 id='%s'><a href='%s.html'>%s</a></h3>",
|
2021-04-15 18:44:07 +00:00
|
|
|
scsw(filename, ' ', '_'),
|
|
|
|
filename,
|
|
|
|
s);
|
2021-04-15 18:40:40 +00:00
|
|
|
fpinject(f, l, srcpath);
|
|
|
|
l->refs[target]++;
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2021-04-17 19:02:02 +00:00
|
|
|
int fpimg(FILE* f, char* s) {
|
|
|
|
char id[1024] = {0};
|
|
|
|
char src[1024] = {0};
|
|
|
|
char alt[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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fprintf(f, "<a id='%s' href='%s'>\n", id, src);
|
|
|
|
fprintf(f, "<img class='image' src='%s' alt='%s'/>\n", src, alt);
|
|
|
|
fputs("</a>", f);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
int fpimgl(FILE* f, char* s) {
|
|
|
|
char id[1024] = {0};
|
|
|
|
char href[1024] = {0};
|
|
|
|
char src[1024] = {0};
|
|
|
|
char alt[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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fprintf(f, "<a id='%s' href='%s'>\n", id, href);
|
|
|
|
fprintf(f, "<img class='image' src='%s' alt='%s'/>\n", src, 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;
|
|
|
|
}
|
|
|
|
|
2021-04-15 18:44:07 +00:00
|
|
|
int fptemplate(FILE* f, Lexicon* l, char* s) {
|
2021-04-15 18:40:40 +00:00
|
|
|
int target;
|
|
|
|
if (s[0] == '/')
|
|
|
|
return fpportal(f, l, s + 1, 1);
|
2021-04-17 19:02:02 +00:00
|
|
|
if (s[0] == ':')
|
|
|
|
return fpimg(f, s + 1);
|
|
|
|
if (s[0] == '?')
|
|
|
|
return fpimgl(f, s + 1);
|
|
|
|
if (s[0] == '_')
|
|
|
|
return fpaudio(f, s + 1);
|
2021-04-15 18:40:40 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2021-04-15 18:44:07 +00:00
|
|
|
int fpinject(FILE* f, Lexicon* l, char* filepath) {
|
|
|
|
FILE* inc;
|
2021-04-15 18:40:40 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2021-04-15 18:44:07 +00:00
|
|
|
FILE* build(FILE* f, Lexicon* l, char* name, char* srcpath) {
|
2021-04-15 18:40:40 +00:00
|
|
|
if (!f)
|
|
|
|
return f;
|
|
|
|
/* begin */
|
|
|
|
fputs("<!DOCTYPE html><html lang='en'>", f);
|
|
|
|
fputs("<head>", f);
|
2021-04-15 18:44:07 +00:00
|
|
|
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");
|
2021-04-15 18:40:40 +00:00
|
|
|
if (ismetanav(name)) {
|
|
|
|
fputs("<link rel='stylesheet' type='text/css' href='" FRONTCSS "'>", f);
|
|
|
|
} else {
|
|
|
|
fputs("<link rel='stylesheet' type='text/css' href='" MAINCSS "'>", f);
|
|
|
|
}
|
2021-04-15 18:44:07 +00:00
|
|
|
fprintf(
|
|
|
|
f,
|
|
|
|
"<link rel='shortcut icon' href='/favicon.ico'>"
|
|
|
|
"<title>" NAME " — %s</title>",
|
2021-04-17 19:02:02 +00:00
|
|
|
ismetanav(name) ? "home" : name);
|
2021-04-15 18:40:40 +00:00
|
|
|
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);
|
|
|
|
/* footer */
|
|
|
|
fputs("<footer>", f);
|
|
|
|
fpedited(f, srcpath);
|
2021-04-15 18:44:07 +00:00
|
|
|
fputs(
|
|
|
|
"contact: <a href='mailto://drkste@zoho.com'>drkste@zoho.com</a> ",
|
|
|
|
f);
|
2021-04-15 18:40:40 +00:00
|
|
|
fputs("(<a href='keys.html'>keys</a>)", f);
|
|
|
|
/* fputs("<a href='" LICENSE "' target='_blank'>BY-NC-SA 4.0</a>", f) */;
|
|
|
|
fputs("</footer>", f);
|
|
|
|
}
|
|
|
|
/* end */
|
|
|
|
fputs("</body></html>", f);
|
|
|
|
return f;
|
|
|
|
}
|
|
|
|
|
2021-04-15 18:44:07 +00:00
|
|
|
int generate(Lexicon* l) {
|
2021-04-15 18:40:40 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2021-04-15 18:44:07 +00:00
|
|
|
int index(Lexicon* l, DIR* d) {
|
2021-04-15 18:40:40 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2021-04-15 18:44:07 +00:00
|
|
|
void inspect(Lexicon* l) {
|
2021-04-15 18:40:40 +00:00
|
|
|
int i;
|
|
|
|
for (i = 0; i < l->len; ++i)
|
|
|
|
if (!l->refs[i])
|
|
|
|
error("Orphaned", l->files[i]);
|
|
|
|
}
|
|
|
|
|
|
|
|
int main(void) {
|
|
|
|
Lexicon lex;
|
2021-04-15 18:44:07 +00:00
|
|
|
DIR* d;
|
2021-04-15 18:40:40 +00:00
|
|
|
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;
|
|
|
|
}
|