colla/tools/docs.c
2024-12-05 18:15:05 +01:00

465 lines
No EOL
12 KiB
C

#include "../arena.c"
#include "../file.c"
#include "../format.c"
#include "../ini.c"
#include "../str.c"
#include "../strstream.c"
#include "../tracelog.c"
#include "../vmem.c"
#include "../markdown.c"
#include "../highlight.c"
#include "../dir.c"
#include "../socket.c"
#include "../http.c"
#include "../server.c"
#include "../html.h"
const char *raw_css;
struct {
bool gen;
strview_t gendir;
} options = {0};
typedef struct page_t {
str_t title;
str_t url;
str_t data;
struct page_t *next;
} page_t;
typedef struct {
uint8 arenabuf[KB(5)];
arena_t arena;
hl_ctx_t *hl;
int line;
} cparser_ctx_t;
void md_cparser_init(void *udata) {
cparser_ctx_t *cparser = udata;
cparser->line = 1;
if (cparser->hl) {
return;
}
cparser->arena = arenaMake(ARENA_STATIC, sizeof(cparser->arenabuf), cparser->arenabuf);
cparser->hl = hlInit(&cparser->arena, &(hl_config_t){
.colors = {
[HL_COLOR_NORMAL] = strv("</span>"),
[HL_COLOR_PREPROC] = strv("<span class=\"c-preproc\">"),
[HL_COLOR_TYPES] = strv("<span class=\"c-types\">"),
[HL_COLOR_CUSTOM_TYPES] = strv("<span class=\"c-custom-types\">"),
[HL_COLOR_KEYWORDS] = strv("<span class=\"c-kwrds\">"),
[HL_COLOR_NUMBER] = strv("<span class=\"c-num\">"),
[HL_COLOR_STRING] = strv("<span class=\"c-str\">"),
[HL_COLOR_COMMENT] = strv("<span class=\"c-cmnt\">"),
[HL_COLOR_FUNC] = strv("<span class=\"c-func\">"),
[HL_COLOR_SYMBOL] = strv("<span class=\"c-sym\">"),
[HL_COLOR_MACRO] = strv("<span class=\"c-macro\">"),
},
.flags = HL_FLAG_HTML,
});
}
void md_cparser_callback(strview_t line, outstream_t *out, void *udata) {
cparser_ctx_t *cparser = udata;
arena_t scratch = cparser->arena;
str_t highlighted = hlHighlight(&scratch, cparser->hl, line);
ostrPrintf(out, "<li>%v</li>", highlighted);
}
page_t *get_pages(arena_t *arena, strview_t path, strview_t default_page) {
arena_t scratch = arenaMake(ARENA_VIRTUAL, MB(1));
dir_t *dir = dirOpen(&scratch, path);
dir_entry_t *entry = NULL;
page_t *first = NULL;
page_t *head = NULL;
page_t *tail = NULL;
cparser_ctx_t cparser = {0};
while ((entry = dirNext(&scratch, dir))) {
if (entry->type != DIRTYPE_FILE) {
continue;
}
strview_t name, ext;
fileSplitPath(strv(entry->name), NULL, &name, &ext);
if (!strvEquals(ext, strv(".md"))) {
continue;
}
str_t fullname = strFmt(&scratch, "%v/%v", path, entry->name);
str_t markdown_str = fileReadWholeStr(&scratch, strv(fullname));
str_t md = markdownStr(&scratch, strv(markdown_str), &(md_options_t){
.parsers = (md_parser_t[]){
{
.init = md_cparser_init,
.callback = md_cparser_callback,
.userdata = &cparser,
.lang = strv("c"),
},
},
.parsers_count = 1,
});
page_t *page = alloc(arena, page_t);
page->data = md;
page->url = str(arena, name);
usize line_end = strvFind(strv(markdown_str), '\n', 0);
strview_t line = strvSub(strv(markdown_str), 0, line_end);
strview_t page_title = strvTrim(strvRemovePrefix(line, 1));
page->title = strFmt(arena, "%v", page_title);
if (!first && strvEquals(name, default_page)) {
first = page;
}
else {
if (!head) head = page;
if (tail) tail->next = page;
tail = page;
}
}
if (first) {
first->next = head;
head = first;
}
strview_t css = strv(raw_css);
for_each(page, head) {
str_t html = STR_EMPTY;
htmlBeg(arena, &html);
headBeg();
title(page->title);
style(css);
headEnd();
bodyBeg();
divBeg(.id="main");
divBeg(.class="content");
divBeg(.class="pages-container");
divBeg(.class="pages");
for_each(item, head) {
str_t class = strFmt(&scratch, "page-item%s", item == page ? " page-current" : "");
str_t href = STR_EMPTY;
if (options.gen) {
href = strFmt(&scratch, "%v.html", item->url);
}
else {
href = strFmt(&scratch, "%v", item->url);
}
a(
item->title,
.href = href.buf,
.class = class.buf,
);
}
divEnd();
divEnd();
divBeg(.class="document-container");
divBeg(.class="document");
htmlPuts(page->data);
htmlRaw(<div class="document-padding"></div>);
divEnd();
divEnd();
divEnd();
divEnd();
bodyEnd();
htmlEnd();
page->data = html;
}
arenaCleanup(&scratch);
return head;
}
str_t server_default(arena_t scratch, server_t *server, server_req_t *req, void *userdata) {
strview_t needle = strv(req->page);
if (strvFront(needle) == '/') {
needle = strvRemovePrefix(strv(req->page), 1);
}
page_t *page = userdata;
while (page) {
if (strvEquals(strv(page->url), needle)) {
break;
}
page = page->next;
}
// if the url is invalid, return the default page
if (!page) {
page = userdata;
}
return serverMakeResponse(&scratch, 200, strv("text/html"), strv(page->data));
}
str_t server_quit(arena_t scratch, server_t *server, server_req_t *req, void *userdata) {
serverStop(server);
return STR_EMPTY;
}
int main(int argc, char **argv) {
arena_t arena = arenaMake(ARENA_VIRTUAL, MB(1));
for (int i = 1; i < argc; ++i) {
strview_t arg = strv(argv[i]);
if (strvEquals(arg, strv("-h"))) {
info("usage: %s [-h, -gen <outdir>]", argv[0]);
return 0;
}
else if (strvEquals(arg, strv("-gen"))) {
options.gen = true;
if ((i + 1) < argc) {
options.gendir = strv(argv[++i]);
}
}
}
page_t *pages = get_pages(&arena, strv("."), strv("readme"));
if (!pages) {
err("could not get pages");
return 1;
}
if (options.gen) {
if (strvBack(options.gendir) == '/' || strvBack(options.gendir) == '\\') {
options.gendir.len -= 1;
}
for_each(page, pages) {
arena_t scratch = arena;
str_t fname = strFmt(&scratch, "%v/%v.html", options.gendir, page->url);
if (!fileWriteWhole(strv(fname), page->data.buf, page->data.len)) {
err("couldn't save page %v", fname);
}
else {
info("saved %v", fname);
}
}
return 0;
}
server_t *s = serverSetup(&arena, 8080, true);
serverRouteDefault(&arena, s, server_default, pages);
serverRoute(&arena, s, strv("/quit"), server_quit, NULL);
serverStart(arena, s);
arenaCleanup(&arena);
}
//// HTML GENERATION STUFF ///////////////////////////////
const char *raw_css = ""
"html, body, #main {\n"
" margin: 0;\n"
" width: 100%;\n"
" height: 100%;\n"
" font-family: -apple-system,BlinkMacSystemFont,'Segoe UI','Noto Sans',Helvetica,Arial,sans-serif,'Apple Color Emoji','Segoe UI Emoji';\n"
" \n"
" --black: #121212;\n"
" --dark-gray: #212121;\n"
" --gray: #303030;\n"
" --light-gray: #424242;\n"
" --accent: #FFA500;\n"
" /* --accent: #F98334; */\n"
"\n"
" --blue: #45559E;\n"
" --orange: #F98334;\n"
" --yellow: #FABD2F;\n"
" --red: #FB4934;\n"
" --pink: #D3869B;\n"
" --green: #B8BB26;\n"
" --azure: #7FA375;\n"
" --white: #FBF1C7;\n"
" --light-blue: #83A598;\n"
"}\n"
"\n"
"hr {\n"
" color: white;\n"
" opacity: 0.5;\n"
"}\n"
"\n"
"a {\n"
" text-decoration: none;\n"
" font-weight: bold;\n"
" color: var(--red);\n"
"}\n"
"\n"
"a:hover {\n"
" text-decoration: underline;\n"
"}\n"
"\n"
".content {\n"
" width: 100%;\n"
" height: 100%;\n"
" background-color: var(--black);\n"
" display: flex;\n"
" gap: 20px;\n"
" align-items: stretch;\n"
" color: white;\n"
" overflow-x: scroll;\n"
"}\n"
"\n"
".pages-container {\n"
" position: sticky;\n"
" top: 0;\n"
" min-width: 200px;\n"
" background-color: var(--dark-gray);\n"
" border-right: 1px solid var(--light-gray);\n"
" box-shadow: 0 0 20px rgba(0, 0, 0, 1.0);\n"
" overflow-y: scroll;\n"
"}\n"
"\n"
".document-container {\n"
" display: flex;\n"
" justify-content: center;\n"
" width: 100%;\n"
"}\n"
"\n"
".document {\n"
" width: 100%;\n"
" max-width: 700px;\n"
" line-height: 1.5;\n"
"}\n"
"\n"
".document-padding {\n"
" height: 50px;\n"
"}\n"
"\n"
".pages {\n"
" position: relative;\n"
" background-color: var(--dark-gray);\n"
"}\n"
"\n"
".page-item:last-of-type {\n"
" border-bottom: 0;\n"
"}\n"
"\n"
".page-item {\n"
" text-decoration: none;\n"
" display: block;\n"
" color: rgba(255, 255, 255, 0.5);\n"
" padding: 0.2em 0 0.2em 1.5em;\n"
" border-bottom: 1px solid var(--light-gray);\n"
"}\n"
"\n"
".page-item:hover {\n"
" background-color: var(--light-gray);\n"
" cursor: pointer;\n"
" color: white;\n"
" opacity: 1;\n"
"}\n"
"\n"
".page-current {\n"
" color: var(--accent);\n"
" opacity: 1;\n"
"}\n"
"\n"
".page-current:hover {\n"
" color: var(--accent);\n"
"}\n"
"\n"
".page-spacing {\n"
" height: 25px; \n"
"}\n"
"\n"
"code {\n"
" color: var(--accent);\n"
" background-color: var(--dark-gray);\n"
" padding: 0.2em 0.5em 0.2em 0.5em;\n"
" border-radius: 0.5em;\n"
"}\n"
"\n"
"pre {\n"
" margin: 0;\n"
" margin-top: 2em;\n"
" background-color: var(--dark-gray);\n"
" padding: 16px;\n"
" border-radius: 0.3em;\n"
" overflow-x: scroll;\n"
"\n"
" border: 1px solid var(--light-gray);\n"
" box-shadow: 0 0 20px rgba(0, 0, 0, 1.0);\n"
"}\n"
"\n"
"pre > code {\n"
" color: #FBF1C7;\n"
" padding: 0;\n"
" background-color: transparent;\n"
"}\n"
"\n"
"pre ol {\n"
" counter-reset: item;\n"
" padding-left: 0;\n"
"}\n"
"\n"
"pre li {\n"
" display: block;\n"
" margin-left: 0em;\n"
"}\n"
"\n"
"pre li::before {\n"
" display: inline-block;\n"
" content: counter(item);\n"
" counter-increment: item;\n"
" width: 2em;\n"
" padding-right: 1.5em;\n"
" text-align: right;\n"
"}\n"
"\n"
"/* code block colors */\n"
".c-preproc {\n"
" color: #45559E;\n"
"}\n"
"\n"
".c-types {\n"
" color: #F98334;\n"
"}\n"
"\n"
".c-custom-types {\n"
" color: #FABD2F;\n"
"}\n"
"\n"
".c-kwrds {\n"
" color: #FB4934;\n"
"}\n"
"\n"
".c-num {\n"
" color: #D3869B;\n"
"}\n"
"\n"
".c-str {\n"
" color: #B8BB26;\n"
"}\n"
"\n"
".c-cmnt {\n"
" color: #928374;\n"
"}\n"
"\n"
".c-func {\n"
" color: #7FA375;\n"
"}\n"
"\n"
".c-sym {\n"
" color: #FBF1C7;\n"
"}\n"
"\n"
".c-macro {\n"
" color: #83A598;\n"
"}\n"
"";