Compare commits

...

13 Commits

30 changed files with 2003 additions and 1119 deletions

2
.gitignore vendored
View File

@ -4,3 +4,5 @@ build
gpp1 gpp1
watcher watcher
commit.h commit.h
compile_flags.txt
bundle.zip

3
.gitmodules vendored
View File

@ -16,3 +16,6 @@
[submodule "md5-c"] [submodule "md5-c"]
path = md5-c path = md5-c
url = https://github.com/Zunawe/md5-c.git url = https://github.com/Zunawe/md5-c.git
[submodule "zip"]
path = zip
url = https://github.com/kuba--/zip.git

View File

@ -3,3 +3,6 @@
./gpp1 ./gpp1
./watcher ./watcher
./mongoose.o ./mongoose.o
./bundle.zip
./compile_flags.txt
./commit.h

View File

@ -7,4 +7,6 @@
# define CONFIG_LISTEN_URL "http://localhost:5000" # define CONFIG_LISTEN_URL "http://localhost:5000"
#endif #endif
#define BUNDLE_ZIP_COMPRESSION 10
#endif // CONFIG_H_ #endif // CONFIG_H_

90
baked.c
View File

@ -3,44 +3,16 @@
#include "gebs/gebs.h" #include "gebs/gebs.h"
#include "incbin/incbin.h" #include "incbin/incbin.h"
#include "stb/stb_ds.h" #include "stb/stb_ds.h"
#include "zip.h"
#include "baked.h" #include "baked.h"
#include "locked.h" #include "locked.h"
#include "CONFIG.h"
INCBIN(gpp1, "./gpp1"); INCBIN(bundle_zip, "./bundle.zip");
INCBIN(home_html, "./tmpls/home.html");
INCBIN(page_missing_html, "./tmpls/page-missing.html");
INCBIN(template_blog_html, "./tmpls/template-blog.html");
INCBIN(blog_html, "./tmpls/blog.html");
INCBIN(simple_css, "./etc/simple.css");
INCBIN(favicon_ico, "./etc/favicon.ico");
#if MY_DEBUG
INCBIN(hotreload_js, "./etc/hotreload.js");
#endif
INCBIN(theme_js, "./etc/theme.js");
INCBIN(me_jpg, "./etc/me.jpg");
INCBIN(tmoa_engine_jpg, "./etc/tmoa-engine.jpg");
INCBIN(tmoa_garbage_jpg, "./etc/tmoa-garbage.jpg");
INCBIN(blog_welcome_md, "./blog/welcome.md");
INCBIN(blog_weird_page_md, "./blog/weird-page.md");
INCBIN(blog_curious_case_of_gebs_md, "./blog/curious-case-of-gebs.md");
INCBIN(blog_the_making_of_aboba_md, "./blog/the-making-of-aboba.md");
static locked(Baked_Resource *) baked_resources = locked_init(nil); static locked(Baked_Resource *) baked_resources = locked_init(nil);
void lock_baked_resources(void)
{
lockx(&baked_resources);
}
void unlock_baked_resources(void)
{
unlockx(&baked_resources);
}
void add_baked_resource(char *key, const uchar *data, size_t size) void add_baked_resource(char *key, const uchar *data, size_t size)
{ {
int fd = memfd_create(key, 0); int fd = memfd_create(key, 0);
@ -49,51 +21,55 @@ void add_baked_resource(char *key, const uchar *data, size_t size)
abort(); abort();
} }
write(fd, data, size); write(fd, data, size);
shput(baked_resources.value, key, fd); shput(baked_resources.value, key, ((Baked_Resource_Value){ .memfd = fd, .bufptr = (void *)data }));
LOGI("Added baked resource: %s\n", key);
} }
void init_baked_resources(void) void init_baked_resources(void)
{ {
lockx(&baked_resources); LOGI("Initializing baked resources...\n");
add_baked_resource("home.html", home_html_data, home_html_size); struct zip_t *zip = zip_stream_open(bundle_zip_data, bundle_zip_size, BUNDLE_ZIP_COMPRESSION, 'r');
add_baked_resource("page-missing.html", page_missing_html_data, page_missing_html_size); size_t n = zip_entries_total(zip);
add_baked_resource("template-blog.html", template_blog_html_data, template_blog_html_size); for (size_t i = 0; i < n; i++) {
add_baked_resource("blog.html", blog_html_data, blog_html_size); zip_entry_openbyindex(zip, i);
add_baked_resource("gpp1", gpp1_data, gpp1_size);
add_baked_resource("simple.css", simple_css_data, simple_css_size); const char *name = strdup(zip_entry_name(zip));
add_baked_resource("favicon.ico", favicon_ico_data, favicon_ico_size); size_t size = zip_entry_size(zip);
#if MY_DEBUG char *buf = malloc(size);
add_baked_resource("hotreload.js", hotreload_js_data, hotreload_js_size); zip_entry_noallocread(zip, buf, size);
#endif
add_baked_resource("theme.js", theme_js_data, theme_js_size); add_baked_resource((char *)name, buf, size);
add_baked_resource("me.jpg", me_jpg_data, me_jpg_size);
add_baked_resource("tmoa-engine.jpg", tmoa_engine_jpg_data, tmoa_engine_jpg_size); zip_entry_close(zip);
add_baked_resource("tmoa-garbage.jpg", tmoa_garbage_jpg_data, tmoa_garbage_jpg_size); }
add_baked_resource("blog-welcome.md", blog_welcome_md_data, blog_welcome_md_size); zip_stream_close(zip);
add_baked_resource("blog-weird-page.md", blog_weird_page_md_data, blog_weird_page_md_size); LOGI("baked resources done\n");
add_baked_resource("blog-curious-case-of-gebs.md", blog_curious_case_of_gebs_md_data, blog_curious_case_of_gebs_md_size);
add_baked_resource("blog-the-making-of-aboba.md", blog_the_making_of_aboba_md_data, blog_the_making_of_aboba_md_size);
unlockx(&baked_resources);
} }
void free_baked_resources(void) void free_baked_resources(void)
{ {
lockx(&baked_resources); LOGI("freeing baked resouces\n");
for (size_t i = 0; i < shlen(baked_resources.value); i++) { for (size_t i = 0; i < shlen(baked_resources.value); i++) {
close(baked_resources.value[i].value); close(baked_resources.value[i].value.memfd);
free(baked_resources.value[i].key);
free(baked_resources.value[i].value.bufptr);
} }
shfree(baked_resources.value); shfree(baked_resources.value);
unlockx(&baked_resources); LOGI("baked resources done\n");
} }
bool get_baked_resource_path(char *key, char *buf, size_t size) bool get_baked_resource_path(char *key, char *buf, size_t size)
{ {
LOGI("Request for baked resource path: %s\n", key);
lockx(&baked_resources);
if (shgeti(baked_resources.value, key) != -1) { if (shgeti(baked_resources.value, key) != -1) {
int fd = shget(baked_resources.value, key); Baked_Resource_Value brv = shget(baked_resources.value, key);
snprintf(buf, size, "/proc/%d/fd/%d", getpid(), fd); snprintf(buf, size, "/proc/%d/fd/%d", getpid(), brv.memfd);
LOGI("Memfd path %s\n", buf);
unlockx(&baked_resources); unlockx(&baked_resources);
return true; return true;
} }
unlockx(&baked_resources);
return false; return false;
} }

31
baked.h
View File

@ -1,40 +1,19 @@
#ifndef BAKED_H_ #ifndef BAKED_H_
#define BAKED_H_ #define BAKED_H_
#include "incbin/incbin.h" typedef struct {
int memfd;
INCBIN_EXTERN(gpp1); void *bufptr;
} Baked_Resource_Value;
INCBIN_EXTERN(home_html);
INCBIN_EXTERN(page_missing_html);
INCBIN_EXTERN(template_blog_html);
INCBIN_EXTERN(blog_html);
INCBIN_EXTERN(simple_css);
INCBIN_EXTERN(favicon_ico);
#if MY_DEBUG
INCBIN_EXTERN(hotreload_js);
#endif
INCBIN_EXTERN(theme_js);
INCBIN_EXTERN(me_jpg);
INCBIN_EXTERN(tmoa_engine_jpg);
INCBIN_EXTERN(tmoa_garbage_jpg);
INCBIN_EXTERN(blog_welcome_md);
INCBIN_EXTERN(blog_weird_page_md);
INCBIN_EXTERN(blog_curious_case_of_gebs_md);
INCBIN_EXTERN(blog_the_making_of_aboba_md);
typedef struct { typedef struct {
char *key; // path char *key; // path
int value; // memfd Baked_Resource_Value value;
} Baked_Resource; } Baked_Resource;
void init_baked_resources(void); void init_baked_resources(void);
void free_baked_resources(void); void free_baked_resources(void);
bool get_baked_resource_path(char *key, char *buf, size_t size); bool get_baked_resource_path(char *key, char *buf, size_t size);
void baked_resource_each(void (*f)(Baked_Resource *resource, void *udata), void *udata); void baked_resource_each(void (*f)(Baked_Resource *resource, void *udata), void *udata);
void lock_baked_resources(void);
void unlock_baked_resources(void);
#endif // BAKED_H_ #endif // BAKED_H_

View File

@ -0,0 +1,273 @@
# Asset packing with [zip.c](https://raw.githubusercontent.com/kuba--/zip/refs/heads/master/src/zip.c)
One of the technical decisions that I had to make when writing this website is the way that I'm going to
distribute the assets (images, GPP, page templates, etc.). I've decided to go with packing or baking-in
the assets into the main binary, so that I don't have to worry about updating an asset folder alongside
the application.
One flaw of this approach is that as the website gets bigger, the binary will too. Fortunately I don't
have to worry about this too much right now, since I've reimplemented asset packing with the excellent
zip library by \`kuba--\` (Polska GUROM!!!111!!). In this article I'd like to demonstrate how I've
minimised the size of our assets using \`zip\` and \`incbin\`.
![zip file](/bakedres/apwz-zip-icon.png)
## References
- zip: https://raw.githubusercontent.com/kuba--/zip/refs/heads/master/src/zip.c
- incbin: https://github.com/graphitemaster/incbin
- zip format: https://en.wikipedia.org/wiki/ZIP_(file_format)
- the source code: https://git.kamkow1lair.pl/kamkow1/aboba
## Generating the bundle - \`bundle.zip\`
To compress our assets, first we need to generate a bundle file. I've decided to call it bundle.zip - simple
and descriptive.
Generating the bundle requires some changes to our build program. See https://git.kamkow1lair.pl/kamkow1/aboba/src/branch/master/build.c for reference.
\`\`\`
#define BUNDLED_FILES \\
"./gpp1", \\
"./tmpls/home.html", \\
"./tmpls/page-missing.html", \\
"./tmpls/template-blog.html", \\
"./tmpls/blog.html", \\
"./etc/hotreload.js", \\
"./etc/theme.js", \\
"./etc/simple.css", \\
"./etc/highlight.js", \\
"./etc/hljs-rainbow.css", \\
"./etc/marked.js", \\
"./etc/favicon.ico", \\
"./etc/me.jpg", \\
"./etc/tmoa-engine.jpg", \\
"./etc/tmoa-garbage.jpg", \\
"./blog/blog-welcome.md", \\
"./blog/blog-weird-page.md", \\
"./blog/blog-curious-case-of-gebs.md", \\
"./blog/blog-the-making-of-aboba.md", \\
"./blog/blog-asset-packing-with-zip.c.md"
const char *bundle_zip_deps[] = { BUNDLED_FILES };
RULE_ARRAY("./bundle.zip", bundle_zip_deps) {
RULE("./gpp1", "./gpp/gpp.c") {
CMD("cc", "-DHAVE_STRDUP", "-DHAVE_FNMATCH_H", "-o", "gpp1", "gpp/gpp.c");
}
struct zip_t *zip = zip_open("./bundle.zip", BUNDLE_ZIP_COMPRESSION, 'w');
defer { zip_close(zip); }
for (size_t i = 0; i < sizeof(bundle_zip_deps)/sizeof(bundle_zip_deps[0]); i++) {
char *copy = strdup(bundle_zip_deps[i]);
defer { free(copy); }
char *name = basename(copy);
String_Builder sb = {0};
defer { sb_free(&sb); }
sb_read_file(&sb, bundle_zip_deps[i]);
zip_entry_open(zip, name);
zip_entry_write(zip, sb.items, sb.count);
zip_entry_close(zip);
}
LOGI("Generated bundle.zip\\n");
}
RULE("./aboba",
"./main.c",
"./routes.c",
"./routes.h",
"./baked.c",
"./baked.h",
"./commit.h",
"./timer.c",
"./timer.h",
"./CONFIG.h",
"./locked.h",
"./mongoose.o",
"./bundle.zip",
BUNDLED_FILES
) {
// build mongoose.o - skipped
// Generate commit.h - skipped
#define CC "cc"
#define TARGET "-o", "aboba"
#if MY_DEBUG
#define CFLAGS "-fsanitize=address", "-fPIC", "-ggdb"
#define DEFINES "-DMY_DEBUG=1", "-D_GNU_SOURCE", "-DGEBS_NO_PREFIX", "-DINCBIN_PREFIX=", "-DINCBIN_STYLE=INCBIN_STYLE_SNAKE", \\
"-DGEBS_ENABLE_PTHREAD_FEATURES"
#define EXTRA_SOURCES "./cJSON/cJSON.c", "./zip/src/zip.c", "./md5-c/md5.c"
#else
#define CFLAGS "-fPIC"
#define DEFINES "-DMY_DEBUG=0", "-D_GNU_SOURCE", "-DGEBS_NO_PREFIX", "-DINCBIN_PREFIX=", "-DINCBIN_STYLE=INCBIN_STYLE_SNAKE", \\
"-DGEBS_ENABLE_PTHREAD_FEATURES"
#define EXTRA_SOURCES "./cJSON/cJSON.c", "./zip/src/zip.c"
#endif
#define SOURCES "./main.c", "./routes.c", "./baked.c", "./timer.c"
#define OBJECTS "./mongoose.o"
#define LINK_FLAGS "-Wl,-z,execstack", "-lpthread"
#define INC_FLAGS "-I.", "-I./zip/src"
CMD(CC, TARGET, CFLAGS, DEFINES, INC_FLAGS, SOURCES, OBJECTS, EXTRA_SOURCES, LINK_FLAGS);
// generate compile_flags.txt - skipped
// #undef macros - skipped
}
\`\`\`
If you go through the commit history, you'll see that apart from just generating the bundle file, I've also cleaned up the
build commands a bit with \`#define\`s. Let's take a closer look at the bundle generation code.
\`\`\`
const char *bundle_zip_deps[] = { BUNDLED_FILES };
RULE_ARRAY("./bundle.zip", bundle_zip_deps) {
RULE("./gpp1", "./gpp/gpp.c") {
CMD("cc", "-DHAVE_STRDUP", "-DHAVE_FNMATCH_H", "-o", "gpp1", "gpp/gpp.c");
}
struct zip_t *zip = zip_open("./bundle.zip", BUNDLE_ZIP_COMPRESSION, 'w');
defer { zip_close(zip); }
for (size_t i = 0; i < sizeof(bundle_zip_deps)/sizeof(bundle_zip_deps[0]); i++) {
char *copy = strdup(bundle_zip_deps[i]);
defer { free(copy); }
char *name = basename(copy);
String_Builder sb = {0};
defer { sb_free(&sb); }
sb_read_file(&sb, bundle_zip_deps[i]);
zip_entry_open(zip, name);
zip_entry_write(zip, sb.items, sb.count);
zip_entry_close(zip);
}
LOGI("Generated bundle.zip\\n");
}
\`\`\`
We declare a dependency (using \`RULE\*()\` macro), which says that \`./bundle.zip\` depends on files defined by \`BUNDLED_FILES\`.
To generate the bundle we use \`zip_open()\` \`zip_close()\`. To call \`zip_open()\`, we have to provide a so called "compression level".
The zip library provides us only with \`ZIP_DEFAULT_COMPRESSION_LEVEL\`, which is a macro that evaluates to 6. I wasn't satisfied with
it, so after looking at \`miniz.h\` (a backing library that zip uses), I've found that zip uses \`MZ_DEFAULT_COMPRESSION_LEVEL\`, which
is 6, but we can use the value of \`MZ_UBER_COMPRESSION\`, which is 10. This way we can achieve the most size-efficient compression.
I've decided to \`#define\` the compression level to avoid using arbitrary magic numbers and we can also use that definition both in
the application and in the build program. Here's how \`CONFIG.h\` looks like now:
\`\`\`
#ifndef CONFIG_H_
#define CONFIG_H_
#if MY_DEBUG
# define CONFIG_LISTEN_URL "http://localhost:8080"
#else
# define CONFIG_LISTEN_URL "http://localhost:5000"
#endif
#define BUNDLE_ZIP_COMPRESSION 10
#endif // CONFIG_H_
\`\`\`
The only "downside" here is that, since we're compressing so hard, it's going to take more time to generate the bundle. I've put "downside"
in quotes for purpose, because this does not apply in our case. The files that we're packing are quite small already and there aren't
many of them. We're just doing this to sqeeze out *extra* spacial performance.
Previously we were baking-in the assets like so:
from https://git.kamkow1lair.pl/kamkow1/aboba/src/commit/447362c74dda85838d37d6ee3cb218697abf6106/baked.c
\`\`\`
INCBIN(gpp1, "./gpp1");
INCBIN(home_html, "./tmpls/home.html");
INCBIN(page_missing_html, "./tmpls/page-missing.html");
INCBIN(template_blog_html, "./tmpls/template-blog.html");
INCBIN(blog_html, "./tmpls/blog.html");
INCBIN(simple_css, "./etc/simple.css");
INCBIN(favicon_ico, "./etc/favicon.ico");
#if MY_DEBUG
INCBIN(hotreload_js, "./etc/hotreload.js");
#endif
INCBIN(theme_js, "./etc/theme.js");
INCBIN(highlight_js, "./etc/highlight.js");
INCBIN(hljs_rainbow_css, "./etc/hljs-rainbow.css");
INCBIN(marked_js, "./etc/marked.js");
INCBIN(me_jpg, "./etc/me.jpg");
INCBIN(tmoa_engine_jpg, "./etc/tmoa-engine.jpg");
INCBIN(tmoa_garbage_jpg, "./etc/tmoa-garbage.jpg");
INCBIN(blog_welcome_md, "./blog/welcome.md");
INCBIN(blog_weird_page_md, "./blog/weird-page.md");
INCBIN(blog_curious_case_of_gebs_md, "./blog/curious-case-of-gebs.md");
INCBIN(blog_the_making_of_aboba_md, "./blog/the-making-of-aboba.md");
\`\`\`
Now that we have our \`bundle.zip\`, we do it like this:
\`\`\`
INCBIN(bundle_zip, "./bundle.zip");
\`\`\`
And there we go, we have our bundle!
I've also had to slightly change the way we add the assets to the resource hash table:
\`\`\`
void add_baked_resource(char *key, const uchar *data, size_t size)
{
int fd = memfd_create(key, 0);
if (fd < 0) {
LOGE("Could not create resource %s. Aborting...\n", key);
abort();
}
write(fd, data, size);
shput(baked_resources.value, key, ((Baked_Resource_Value){ .memfd = fd, .bufptr = (void *)data }));
}
void init_baked_resources(void)
{
struct zip_t *zip = zip_stream_open(bundle_zip_data, bundle_zip_size, BUNDLE_ZIP_COMPRESSION, 'r');
size_t n = zip_entries_total(zip);
for (size_t i = 0; i < n; i++) {
zip_entry_openbyindex(zip, i);
const char *name = strdup(zip_entry_name(zip));
size_t size = zip_entry_size(zip);
char *buf = malloc(size);
zip_entry_noallocread(zip, buf, size);
add_baked_resource((char *)name, buf, size);
zip_entry_close(zip);
}
zip_stream_close(zip);
}
\`\`\`
\`add_baked_resource()\` hasn't changed here, but \`init_baked_resources()\` has. Here we use one of zip's abilities,
which is unpacking an in-memory .zip file. We iterate each entry in the bundle, get the name and size, preallocate
a buffer and read said entry into the buffer. We can then add the resource to the hash table as we did previously.
## Conclusion
![xkcd](/bakedres/apwz-xkcd1.png)
One question you may ask is, how much space are we saving? I don't remeber the exact sizes of the binary, but
I remember that before compression it was \`~1.2M\` and now after compression is implemented, it's \`~1.6M\`. How is
that an improvement if the binary gained weight? That \`~.4M\` is likely due to zip format overhead - metadata, file and
directory headers and so on. What we gain here is that the binary hasn't changed in size much despite adding more files,
like for eg. this article that I'm writing right now. It has stayed at \`~1.6M\` so far and the size doesn't go up.
*(So far)* We aren't even making byte-sized gains, which can be checked with \`stat --printf "%s" ./aboba\`. What
we're gaining here is slowed down size increase. Previously if I wanted to add a 30K jpeg, the binary would literally
go up by 30K.

View File

@ -5,7 +5,7 @@ how the code is architectured and some cool tricks that are used throughout the
## Our "engine" ## Our "engine"
![the engine](/etc/tmoa-engine.jpg) ![the engine](/bakedres/tmoa-engine.jpg)
This image is a joke, obviously. This image is a joke, obviously.
@ -133,7 +133,7 @@ void *route_thread_function(void *param)
// Unparsable HTTP request // Unparsable HTTP request
} }
if (mg_match(http_msg.uri, mg_str("/etc/*"), nil)) { if (mg_match(http_msg.uri, mg_str("/bakedres/*"), nil)) {
// Request for static resource // Request for static resource
} }
@ -147,7 +147,7 @@ this functionality comes with mongoose built-in. God bless you mongoose!
## Dynamic pages and static assets. ## Dynamic pages and static assets.
![the assets](/etc/tmoa-garbage.jpg) ![the assets](/bakedres/tmoa-garbage.jpg)
Let's stop here now and talk about something different entirely (dont' worry, we'll come back later). Let's stop here now and talk about something different entirely (dont' worry, we'll come back later).
I'd like to show you how assets/pages are implemented in aboba, so we get the whole picture. I'd like to show you how assets/pages are implemented in aboba, so we get the whole picture.

120
build.c
View File

@ -1,6 +1,9 @@
#include <libgen.h>
#define GEBS_NO_PREFIX #define GEBS_NO_PREFIX
#define GEBS_IMPLEMENTATION #define GEBS_IMPLEMENTATION
#include "gebs/gebs.h" #include "gebs/gebs.h"
#include "./zip/src/zip.c"
#include "CONFIG.h"
char *prog = NULL; char *prog = NULL;
@ -15,13 +18,62 @@ int main(int argc, char ** argv)
prog = SHIFT(&argc, &argv); prog = SHIFT(&argc, &argv);
char *cmd = SHIFT(&argc, &argv); char *cmd = SHIFT(&argc, &argv);
if (strcmp(cmd, "make") == 0) { if (strcmp(cmd, "make") == 0) {
#define BUNDLED_FILES \
"./gpp1", \
"./tmpls/home.html", \
"./tmpls/page-missing.html", \
"./tmpls/template-blog.html", \
"./tmpls/blog.html", \
"./etc/hotreload.js", \
"./etc/theme.js", \
"./etc/simple.css", \
"./etc/highlight.js", \
"./etc/hljs-rainbow.css", \
"./etc/marked.js", \
"./etc/favicon.ico", \
"./etc/me.jpg", \
"./etc/tmoa-engine.jpg", \
"./etc/tmoa-garbage.jpg", \
"./etc/apwz-zip-icon.png", \
"./etc/apwz-xkcd1.png", \
"./blog/blog-welcome.md", \
"./blog/blog-weird-page.md", \
"./blog/blog-curious-case-of-gebs.md", \
"./blog/blog-the-making-of-aboba.md", \
"./blog/blog-asset-packing-with-zip.c.md"
const char *bundle_zip_deps[] = { BUNDLED_FILES };
RULE_ARRAY("./bundle.zip", bundle_zip_deps) {
RULE("./gpp1", "./gpp/gpp.c") {
CMD("cc", "-DHAVE_STRDUP", "-DHAVE_FNMATCH_H", "-o", "gpp1", "gpp/gpp.c");
}
struct zip_t *zip = zip_open("./bundle.zip", BUNDLE_ZIP_COMPRESSION, 'w');
defer { zip_close(zip); }
for (size_t i = 0; i < sizeof(bundle_zip_deps)/sizeof(bundle_zip_deps[0]); i++) {
char *copy = strdup(bundle_zip_deps[i]);
defer { free(copy); }
char *name = basename(copy);
String_Builder sb = {0};
defer { sb_free(&sb); }
sb_read_file(&sb, bundle_zip_deps[i]);
zip_entry_open(zip, name);
zip_entry_write(zip, sb.items, sb.count);
zip_entry_close(zip);
}
LOGI("Generated bundle.zip\n");
}
RULE("./aboba", RULE("./aboba",
"./main.c", "./main.c",
"./routes.c", "./routes.c",
"./routes.h", "./routes.h",
"./baked.c", "./baked.c",
"./baked.h", "./baked.h",
"./clonestr.h",
"./commit.h", "./commit.h",
"./timer.c", "./timer.c",
"./timer.h", "./timer.h",
@ -29,25 +81,9 @@ int main(int argc, char ** argv)
"./locked.h", "./locked.h",
"./mongoose.o", "./mongoose.o",
"./gpp1",
"./tmpls/home.html", "./bundle.zip",
"./tmpls/page-missing.html", BUNDLED_FILES
"./tmpls/template-blog.html",
"./tmpls/blog.html",
"./etc/hotreload.js",
"./etc/theme.js",
"./etc/simple.css",
"./etc/favicon.ico",
"./etc/me.jpg",
"./etc/tmoa-engine.jpg",
"./etc/tmoa-garbage.jpg",
"./blog/welcome.md",
"./blog/weird-page.md",
"./blog/curious-case-of-gebs.md",
"./blog/the-making-of-aboba.md"
) { ) {
RULE("./mongoose.o", "./mongoose/mongoose.c") { RULE("./mongoose.o", "./mongoose/mongoose.c") {
@ -60,10 +96,6 @@ int main(int argc, char ** argv)
#endif #endif
} }
RULE("./gpp1", "./gpp/gpp.c") {
CMD("cc", "-DHAVE_STRDUP", "-DHAVE_FNMATCH_H", "-o", "gpp1", "gpp/gpp.c");
}
RULE("./commit.h") { RULE("./commit.h") {
String_Builder commit = {0}; String_Builder commit = {0};
defer { sb_free(&commit); } defer { sb_free(&commit); }
@ -92,18 +124,39 @@ int main(int argc, char ** argv)
} }
} }
#define CC "cc"
#define TARGET "-o", "aboba"
#if MY_DEBUG #if MY_DEBUG
CMD("cc", "-fsanitize=address", "-fPIC", "-ggdb", "-I.", "-DMY_DEBUG=1", "-D_GNU_SOURCE", "-DGEBS_NO_PREFIX", #define CFLAGS "-fsanitize=address", "-fPIC", "-ggdb"
"-DINCBIN_PREFIX=", "-DINCBIN_STYLE=INCBIN_STYLE_SNAKE", "-Wl,-z,execstack", "-o", "./aboba", #define DEFINES "-DMY_DEBUG=1", "-D_GNU_SOURCE", "-DGEBS_NO_PREFIX", "-DINCBIN_PREFIX=", "-DINCBIN_STYLE=INCBIN_STYLE_SNAKE", \
"./main.c", "./routes.c", "./baked.c", "./timer.c", "./mongoose.o", "./cJSON/cJSON.c", "-DGEBS_ENABLE_PTHREAD_FEATURES"
"./md5-c/md5.c", #define EXTRA_SOURCES "./cJSON/cJSON.c", "./zip/src/zip.c", "./md5-c/md5.c"
"-lpthread");
#else #else
CMD("cc", "-fPIC", "-I.", "-DMY_DEBUG=0", "-D_GNU_SOURCE", "-DGEBS_NO_PREFIX", #define CFLAGS "-fPIC"
"-DINCBIN_PREFIX=", "-DINCBIN_STYLE=INCBIN_STYLE_SNAKE", "-Wl,-z,execstack", "-o", "./aboba", #define DEFINES "-DMY_DEBUG=0", "-D_GNU_SOURCE", "-DGEBS_NO_PREFIX", "-DINCBIN_PREFIX=", "-DINCBIN_STYLE=INCBIN_STYLE_SNAKE", \
"./main.c", "./routes.c", "./baked.c", "./timer.c", "./mongoose.o", "./cJSON/cJSON.c", "-DGEBS_ENABLE_PTHREAD_FEATURES"
"-lpthread"); #define EXTRA_SOURCES "./cJSON/cJSON.c", "./zip/src/zip.c"
#endif #endif
#define SOURCES "./main.c", "./routes.c", "./baked.c", "./timer.c"
#define OBJECTS "./mongoose.o"
#define LINK_FLAGS "-Wl,-z,execstack", "-lpthread"
#define INC_FLAGS "-I.", "-I./zip/src"
CMD(CC, TARGET, CFLAGS, DEFINES, INC_FLAGS, SOURCES, OBJECTS, EXTRA_SOURCES, LINK_FLAGS);
RULE("./compile_flags.txt") {
make_compile_flags(TARGET, CFLAGS, DEFINES, INC_FLAGS, SOURCES, OBJECTS, EXTRA_SOURCES, LINK_FLAGS);
}
#undef CC
#undef TARGET
#undef CFLAGS
#undef DEFINES
#undef EXTRA_SOURCES
#undef SOURCES
#undef OBJECTS
#undef LINK_FLAGS
#undef INC_FLAGS
} }
} else if (strcmp(cmd, "clean") == 0) { } else if (strcmp(cmd, "clean") == 0) {
remove1("./build"); remove1("./build");
@ -111,6 +164,9 @@ int main(int argc, char ** argv)
remove1("./mongoose.o"); remove1("./mongoose.o");
remove1("./gpp1"); remove1("./gpp1");
remove1("./watcher"); remove1("./watcher");
remove1("./compile_flags.txt");
remove1("./bundle.zip");
remove1("./commit.h");
} else if (strcmp(cmd, "make-watcher") == 0) { } else if (strcmp(cmd, "make-watcher") == 0) {
CMD("cc", "-o", "watcher", "watcher.c"); CMD("cc", "-o", "watcher", "watcher.c");
} else if (strcmp(cmd, "watch") == 0) { } else if (strcmp(cmd, "watch") == 0) {

View File

@ -1,11 +1,5 @@
#ifndef CLONESTR_H_ #ifndef CLONESTR_H_
#define CLONESTR_H_ #define CLONESTR_H_
#define clonestr(str) \
({ \
char *__p = malloc(strlen((str))+1); \
strcpy(__p, (str)); \
(__p); \
})
#endif // CLONESTR_H_ #endif // CLONESTR_H_

BIN
etc/apwz-xkcd1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

BIN
etc/apwz-zip-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

1244
etc/highlight.js Normal file

File diff suppressed because one or more lines are too long

1
etc/hljs-rainbow.css Normal file
View File

@ -0,0 +1 @@
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{background:#474949;color:#d1d9e1}.hljs-comment,.hljs-quote{color:#969896;font-style:italic}.hljs-addition,.hljs-keyword,.hljs-literal,.hljs-selector-tag,.hljs-type{color:#c9c}.hljs-number,.hljs-selector-attr,.hljs-selector-pseudo{color:#f99157}.hljs-doctag,.hljs-regexp,.hljs-string{color:#8abeb7}.hljs-built_in,.hljs-name,.hljs-section,.hljs-title{color:#b5bd68}.hljs-class .hljs-title,.hljs-selector-id,.hljs-template-variable,.hljs-title.class_,.hljs-variable{color:#fc6}.hljs-name,.hljs-section,.hljs-strong{font-weight:700}.hljs-bullet,.hljs-link,.hljs-meta,.hljs-subst,.hljs-symbol{color:#f99157}.hljs-deletion{color:#dc322f}.hljs-formula{background:#eee8d5}.hljs-attr,.hljs-attribute{color:#81a2be}.hljs-emphasis{font-style:italic}

69
etc/marked.js Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -82,12 +82,29 @@ function neon_cyber()
document.documentElement.style.setProperty("--disabled", "#333333"); document.documentElement.style.setProperty("--disabled", "#333333");
} }
function dark_easy()
{
document.documentElement.style.setProperty("--bg", "#1A1A1A");
document.documentElement.style.setProperty("--accent-bg", "#2E2E2E");
document.documentElement.style.setProperty("--text", "#DCDCDC");
document.documentElement.style.setProperty("--text-light", "#A3A3A3");
document.documentElement.style.setProperty("--border", "#585858");
document.documentElement.style.setProperty("--accent", "#FF5722");
document.documentElement.style.setProperty("--accent-hover", "#F4511E");
document.documentElement.style.setProperty("--accent-text", "#1A1A1A");
document.documentElement.style.setProperty("--code", "#8BC34A");
document.documentElement.style.setProperty("--preformatted", "#A3A3A3");
document.documentElement.style.setProperty("--marked", "#FF7043");
document.documentElement.style.setProperty("--disabled", "#383838");
}
const themes = { const themes = {
"light-breeze": light_breeze, "light-breeze": light_breeze,
"plan9-acme": plan9_acme, "plan9-acme": plan9_acme,
"bold-navy": bold_navy, "bold-navy": bold_navy,
"soft-lavander": soft_lavander, "soft-lavander": soft_lavander,
"neon-cyber": neon_cyber, "neon-cyber": neon_cyber,
"dark-easy": dark_easy,
}; };

2
gebs

Submodule gebs updated: 6db36114d5...db8389f7d5

View File

@ -3,7 +3,7 @@
#include <pthread.h> #include <pthread.h>
#define locked_init(x) { .value = (x), .lock = PTHREAD_MUTEX_INITIALIZER } #define locked_init(x) { .value = x, .lock = PTHREAD_MUTEX_INITIALIZER }
#define locked(T) struct { T value; pthread_mutex_t lock; } #define locked(T) struct { T value; pthread_mutex_t lock; }
#define lockx(x) pthread_mutex_lock(&(x)->lock) #define lockx(x) pthread_mutex_lock(&(x)->lock)

238
main.c
View File

@ -9,23 +9,39 @@
#include "mongoose/mongoose.h" #include "mongoose/mongoose.h"
#define STB_DS_IMPLEMENTATION #define STB_DS_IMPLEMENTATION
#include "stb/stb_ds.h" #include "stb/stb_ds.h"
#include "md5-c/md5.h"
#include "routes.h" #include "routes.h"
#include "baked.h" #include "baked.h"
#include "timer.h" #include "timer.h"
#include "CONFIG.h" #include "CONFIG.h"
#include "clonestr.h"
#include "locked.h" #include "locked.h"
static locked(Route *) route_hashtable = locked_init(nil); static locked(Route *) route_hashtable = locked_init(nil);
static locked(char *) etc_dump_path = locked_init(nil); static locked(char *) baked_dump_path = locked_init(nil);
char *clonestr_alloc(Allocator *alloc, char *s)
{
char *p = gebs_malloc(alloc, strlen(s)+1);
strcpy(p, s);
return p;
}
void *zmalloc_alloc(Allocator *alloc, size_t size)
{
void *p = gebs_malloc(alloc, size);
memset(p, 0, size);
return p;
}
void run_in_thread(void *(*f)(void *), void *p) void run_in_thread(void *(*f)(void *), void *p)
{ {
pthread_t tid = 0; pthread_t tid = 0;
pthread_attr_t attr; pthread_attr_t attr;
pthread_attr_init(&attr); pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
pthread_create(&tid, &attr, f, p); pthread_create(&tid, &attr, f, p);
pthread_attr_destroy(&attr); pthread_attr_destroy(&attr);
} }
@ -33,91 +49,91 @@ void run_in_thread(void *(*f)(void *), void *p)
void *route_thread_function(void *param) void *route_thread_function(void *param)
{ {
Route_Thread_Data *data = (Route_Thread_Data *)param; Route_Thread_Data *data = (Route_Thread_Data *)param;
Arena *arena = data->arena;
struct mg_http_message http_msg = {0}; struct mg_http_message http_msg = {0};
int r = mg_http_parse(data->message.buf, data->message.len, &http_msg); int r = mg_http_parse(data->message.buf, data->message.len, &http_msg);
if (r <= 0) { if (r <= 0) {
Route_Result result = {0}; Route_Result *result = zmalloc_alloc((Allocator *)arena, sizeof(*result));
result.status_code = 400; result->arena = arena;
result.type = ROUTE_RESULT_DYNAMIC; result->status_code = 400;
/* list_append(&result.headers, clonestr("Content-Type: text/plain")); */ result->type = ROUTE_RESULT_DYNAMIC;
sb_append_nstr(&result.body, "Could not parse HTTP request"); sb_append_nstr_alloc(arena, &result->body, "Could not parse HTTP request");
sb_finish(&result.body); sb_finish_alloc(arena, &result->body);
mg_wakeup(data->mgr, data->conn_id, &result, sizeof(result)); mg_wakeup(data->mgr, data->conn_id, &result, sizeof(result));
free(data->message.buf); free(data->message.buf);
free(data); free(data);
return nil; pthread_exit(nil);
} else {
if (mg_match(http_msg.uri, mg_str("/bakedres/*"), nil)) {
Route_Result *result = zmalloc_alloc((Allocator *)arena, sizeof(*result));
result->arena = arena;
result->type = ROUTE_RESULT_STATIC;
char *p = gebs_malloc(arena, http_msg.uri.len+1);
strncpy(p, http_msg.uri.buf, http_msg.uri.len);
p[http_msg.uri.len] = 0;
char *file = basename(p);
char *full_path = gebs_malloc(arena, PATH_MAX);
lockx(&baked_dump_path);
snprintf(full_path, PATH_MAX, "%s/%s", baked_dump_path.value, file);
unlockx(&baked_dump_path);
sb_append_nstr_alloc(arena, &result->body, full_path);
sb_finish_alloc(arena, &result->body);
mg_wakeup(data->mgr, data->conn_id, &result, sizeof(result));
free(data->message.buf);
free(data);
pthread_exit(nil);
} else {
char key[MG_PATH_MAX] = {0};
strncpy(key, http_msg.uri.buf, http_msg.uri.len);
lockx(&route_hashtable);
ssize_t idx = shgeti(route_hashtable.value, key);
Route_Result *result = zmalloc_alloc((Allocator *)arena, sizeof(*result));
result->arena = arena;
route_hashtable.value[idx].value((Allocator *)arena, &http_msg, result, route_hashtable.value[idx].context_data);
unlockx(&route_hashtable);
mg_wakeup(data->mgr, data->conn_id, &result, sizeof(result));
free(data->message.buf);
free(data);
pthread_exit(nil);
}
} }
if (mg_match(http_msg.uri, mg_str("/etc/*"), nil)) {
Route_Result result = {0};
result.type = ROUTE_RESULT_STATIC;
char *p = malloc(http_msg.uri.len+1);
strncpy(p, http_msg.uri.buf, http_msg.uri.len);
p[http_msg.uri.len] = 0;
char *file = basename(p);
char *full_path = malloc(PATH_MAX);
lockx(&etc_dump_path);
snprintf(full_path, PATH_MAX, "%s/%s", etc_dump_path.value, file);
unlockx(&etc_dump_path);
sb_append_nstr(&result.body, full_path);
sb_finish(&result.body);
free(full_path);
free(p);
mg_wakeup(data->mgr, data->conn_id, &result, sizeof(result));
free(data->message.buf);
free(data);
return nil;
}
char key[MG_PATH_MAX] = {0};
strncpy(key, http_msg.uri.buf, http_msg.uri.len);
lockx(&route_hashtable);
ssize_t idx = shgeti(route_hashtable.value, key);
Route_Result result = {0};
route_hashtable.value[idx].value(&http_msg, &result, route_hashtable.value[idx].context_data);
unlockx(&route_hashtable);
mg_wakeup(data->mgr, data->conn_id, &result, sizeof(result));
free(data->message.buf);
free(data);
return nil; return nil;
} }
void event_handler(struct mg_connection *conn, int ev, void *ev_data) void event_handler(struct mg_connection *conn, int ev, void *ev_data)
{ {
if (ev == MG_EV_HTTP_MSG) { if (ev == MG_EV_HTTP_MSG) {
LOGI("HTTP EVENT\n");
struct mg_http_message *msg = (struct mg_http_message *)ev_data; struct mg_http_message *msg = (struct mg_http_message *)ev_data;
LOGI("Route: %.*s\n", msg->uri.len, msg->uri.buf);
Route_Thread_Data *data = calloc(1, sizeof(*data)); Route_Thread_Data *data = calloc(1, sizeof(*data));
data->message = mg_strdup(msg->message); data->message = mg_strdup(msg->message);
data->conn_id = conn->id; data->conn_id = conn->id;
data->mgr = conn->mgr; data->mgr = conn->mgr;
data->arena = malloc(sizeof(*data->arena));
*data->arena = arena_get();
LOGI("starting handler thread...\n");
run_in_thread(&route_thread_function, data); run_in_thread(&route_thread_function, data);
} else if (ev == MG_EV_WAKEUP) { } else if (ev == MG_EV_WAKEUP) {
LOGI("WAKEUP EVENT\n");
struct mg_str *data = (struct mg_str *)ev_data; struct mg_str *data = (struct mg_str *)ev_data;
Route_Result *result = (Route_Result *)data->buf; Route_Result *result = *(Route_Result **)data->buf;
defer { Arena *arena = result->arena;
for (size_t i = 0; i < result->headers.count; i++) {
free(result->headers.items[i]);
}
list_free(&result->headers);
sb_free(&result->body);
}
Gebs_String_Builder sb = {0}; Gebs_String_Builder sb = {0};
defer { sb_free(&sb); } nsl_join_alloc(arena, &result->headers, &sb, "\r\n");
nsl_join(&result->headers, &sb, "\r\n");
if (result->headers.count > 0) { if (result->headers.count > 0) {
sb_append_nstr(&sb, "\r\n"); sb_append_nstr_alloc(arena, &sb, "\r\n");
} }
sb_finish(&sb); sb_finish_alloc(arena, &sb);
if (result->type == ROUTE_RESULT_DYNAMIC) { if (result->type == ROUTE_RESULT_DYNAMIC) {
mg_http_reply(conn, result->status_code, sb.items, "%s", result->body.items); mg_http_reply(conn, result->status_code, sb.items, "%s", result->body.items);
@ -125,6 +141,11 @@ void event_handler(struct mg_connection *conn, int ev, void *ev_data)
char *path = result->body.items; char *path = result->body.items;
mg_http_serve_file(conn, ev_data, path, &(struct mg_http_serve_opts){0}); mg_http_serve_file(conn, ev_data, path, &(struct mg_http_serve_opts){0});
} }
arena_destroy(arena);
free(arena);
LOGI("WAKEUP done, arena cleaned\n");
} }
} }
@ -142,46 +163,42 @@ void route_hashtable_put_blogs(Baked_Resource *resource, void *udata)
void init_route_hashtable(void) void init_route_hashtable(void)
{ {
lockx(&route_hashtable);
shdefault(route_hashtable.value, &route_page_not_found); shdefault(route_hashtable.value, &route_page_not_found);
shput(route_hashtable.value, clonestr("/"), &route_home); shput(route_hashtable.value, clonestr_alloc(&default_allocator, "/"), &route_home);
shput(route_hashtable.value, clonestr("/page-missing"), &route_page_not_found); shput(route_hashtable.value, clonestr_alloc(&default_allocator, "/page-missing"), &route_page_not_found);
shput(route_hashtable.value, clonestr("/blog"), &route_blog); shput(route_hashtable.value, clonestr_alloc(&default_allocator, "/blog"), &route_blog);
#if MY_DEBUG #if MY_DEBUG
shput(route_hashtable.value, clonestr("/build-id"), &route_build_id); shput(route_hashtable.value, clonestr_alloc(&default_allocator, "/build-id"), &route_build_id);
#endif #endif
baked_resource_each(&route_hashtable_put_blogs, nil); baked_resource_each(&route_hashtable_put_blogs, nil);
unlockx(&route_hashtable);
} }
void free_route_hashtable(void) void free_route_hashtable(void)
{ {
lockx(&route_hashtable);
for (size_t i = 0; i < shlen(route_hashtable.value); i++) { for (size_t i = 0; i < shlen(route_hashtable.value); i++) {
free(route_hashtable.value[i].key); free(route_hashtable.value[i].key);
} }
shfree(route_hashtable.value); shfree(route_hashtable.value);
unlockx(&route_hashtable);
} }
char *init_etc_dump(void) char *init_baked_dump(void)
{ {
char template[] = "/tmp/aboba-etc.XXXXXX"; char template[] = "/tmp/aboba-bakedres.XXXXXX";
char *etc_dump1 = mkdtemp(template); char *baked_dump1 = mkdtemp(template);
char *etc_dump = malloc(strlen(etc_dump1)+1); char *baked_dump = malloc(strlen(baked_dump1)+1);
strcpy(etc_dump, etc_dump1); strcpy(baked_dump, baked_dump1);
if (etc_dump == nil) { if (baked_dump == nil) {
LOGE("Could not create etc dump\n"); LOGE("Could not create bakedres dump\n");
return nil; return nil;
} }
LOGI("etc dump dir is %s\n", etc_dump); LOGI("bakedres dump dir is %s\n", baked_dump);
return etc_dump; return baked_dump;
} }
int etc_dump_rm_cb(const char *path, int baked_dump_rm_cb(const char *path,
discard const struct stat *st, discard const struct stat *st,
discard int type, discard int type,
discard struct FTW *ftw) discard struct FTW *ftw)
@ -189,14 +206,14 @@ int etc_dump_rm_cb(const char *path,
return remove(path); return remove(path);
} }
bool free_etc_dump(char *etc_dump) bool free_baked_dump(char *baked_dump)
{ {
LOGI("Removing etc dump %s\n", etc_dump); LOGI("Removing bakedres dump %s\n", baked_dump);
bool ok = nftw(etc_dump, etc_dump_rm_cb, FOPEN_MAX, FTW_DEPTH | FTW_MOUNT | FTW_PHYS) != -1; bool ok = nftw(baked_dump, baked_dump_rm_cb, FOPEN_MAX, FTW_DEPTH | FTW_MOUNT | FTW_PHYS) != -1;
if (!ok) { if (!ok) {
LOGE("Could not remove %s\n", etc_dump); LOGE("Could not remove %s\n", baked_dump);
} }
free(etc_dump); free(baked_dump);
return ok; return ok;
} }
@ -234,47 +251,40 @@ int cp(const char* source, const char* destination)
return result; return result;
} }
void populate_etc_dump(char *etc_dump) void copy_baked_resources_to_baked_dump(Baked_Resource *resource, void *udata)
{ {
static char *files[] = { char *baked_dump = (char *)udata;
"favicon.ico", char path[PATH_MAX];
#if MY_DEBUG get_baked_resource_path(resource->key, path, sizeof(path));
"hotreload.js", char dest[PATH_MAX];
#endif snprintf(dest, sizeof(dest), "%s/%s", baked_dump, resource->key);
"theme.js", cp(path, dest);
"simple.css",
"me.jpg",
"tmoa-engine.jpg",
"tmoa-garbage.jpg",
};
lock_baked_resources();
for (size_t i = 0; i < sizeof(files)/sizeof(files[0]); i++) {
char path[PATH_MAX];
get_baked_resource_path(files[i], path, sizeof(path));
char dest[PATH_MAX];
snprintf(dest, sizeof(dest), "%s/%s", etc_dump, files[i]);
cp(path, dest);
}
unlock_baked_resources();
} }
volatile bool alive = true; void populate_baked_dump(char *baked_dump)
{
baked_resource_each(&copy_baked_resources_to_baked_dump, baked_dump);
}
volatile sig_atomic_t alive = true;
void graceful_shutdown(int no) { alive = false; } void graceful_shutdown(int no) { alive = false; }
int main(int argc, char ** argv) int main(int argc, char ** argv)
{ {
signal(SIGINT, &graceful_shutdown); struct sigaction sa;
sa.sa_handler = &graceful_shutdown;
sa.sa_flags = 0;
sigemptyset(&sa.sa_mask);
sigaction(SIGINT, &sa, nil);
start_timer(); start_timer();
init_baked_resources(); init_baked_resources();
lockx(&etc_dump_path); if ((baked_dump_path.value = init_baked_dump()) == nil) {
if ((etc_dump_path.value = init_etc_dump()) == nil) {
return 1; return 1;
} }
populate_etc_dump(etc_dump_path.value); populate_baked_dump(baked_dump_path.value);
unlockx(&etc_dump_path);
mg_log_set(MG_LL_DEBUG); mg_log_set(MG_LL_DEBUG);
struct mg_mgr mgr; struct mg_mgr mgr;
@ -292,9 +302,7 @@ int main(int argc, char ** argv)
mg_mgr_free(&mgr); mg_mgr_free(&mgr);
lockx(&etc_dump_path); free_baked_dump(baked_dump_path.value);
free_etc_dump(etc_dump_path.value);
unlockx(&etc_dump_path);
free_baked_resources(); free_baked_resources();
return 0; return 0;

226
routes.c
View File

@ -7,10 +7,11 @@
#include "routes.h" #include "routes.h"
#include "baked.h" #include "baked.h"
#include "clonestr.h"
#include "commit.h" #include "commit.h"
#include "timer.h" #include "timer.h"
extern char *clonestr_alloc(Allocator *alloc, char *s);
#define INTERNAL_SERVER_ERROR_MSG "Internal server error ;(. Try again later..." #define INTERNAL_SERVER_ERROR_MSG "Internal server error ;(. Try again later..."
#define META_TAGS \ #define META_TAGS \
@ -19,65 +20,65 @@
"<meta name=\"author\" content=\"kamkow1\">" \ "<meta name=\"author\" content=\"kamkow1\">" \
"<meta name=\"application-name\" content=\"aboba\">" "<meta name=\"application-name\" content=\"aboba\">"
void make_internal_server_error(Route_Result *result) void make_application_json(Allocator *alloc, Route_Result *result, int code, cJSON *root)
{ {
result->status_code = 500; size_t buf_size = 512;
result->type = ROUTE_RESULT_DYNAMIC; char *buf = gebs_malloc(alloc, buf_size);
list_append(&result->headers, "Content-Type: text/plain");
sb_append_nstr(&result->body, INTERNAL_SERVER_ERROR_MSG);
sb_finish(&result->body);
}
void make_application_json(Route_Result *result, int code, cJSON *root) cJSON_PrintPreallocated(root, buf, buf_size, false);
{
char *root_text = cJSON_PrintUnformatted(root);
defer { free(root_text); }
result->status_code = code; result->status_code = code;
result->type = ROUTE_RESULT_DYNAMIC; result->type = ROUTE_RESULT_DYNAMIC;
list_append(&result->headers, clonestr("Content-Type: application/json")); list_append_alloc(alloc, &result->headers, clonestr_alloc(alloc, "Content-Type: application/json"));
sb_append_nstr(&result->body, root_text); sb_append_nstr_alloc(alloc, &result->body, buf);
sb_finish(&result->body); sb_finish_alloc(alloc, &result->body);
} }
void make_text(Route_Result *result, char *subtype, int code, char *in) void make_text(Allocator *alloc, Route_Result *result, char *subtype, int code, char *in)
{ {
char type[100];
snprintf(type, sizeof(type), "Content-Type: text/%s", subtype);
result->status_code = code; result->status_code = code;
result->type = ROUTE_RESULT_DYNAMIC; result->type = ROUTE_RESULT_DYNAMIC;
list_append(&result->headers, clonestr(type)); list_append_alloc(alloc, &result->headers,
sb_append_nstr(&result->body, in); clonestr_alloc(alloc, fmt("Content-Type: text/%s", subtype)));
sb_append_nstr_alloc(alloc, &result->body, in);
sb_finish(&result->body); sb_finish(&result->body);
} }
bool gpp_run(char *path, NString_List *env, String_Builder *out) void make_internal_server_error(Allocator *alloc, Route_Result *result)
{
make_text(alloc, result, "plain", 500, INTERNAL_SERVER_ERROR_MSG);
}
bool gpp_run(Allocator *alloc, char *path, NString_List *env, String_Builder *out)
{ {
Cmd cmd = {0}; Cmd cmd = {0};
defer { cmd_free(&cmd); }
char gpp1[PATH_MAX]; char gpp1[PATH_MAX];
if (!get_baked_resource_path("gpp1", gpp1, sizeof(gpp1))) { if (!get_baked_resource_path("gpp1", gpp1, sizeof(gpp1))) {
return false; return false;
} }
cmd_append(&cmd, gpp1); cmd_append_alloc(alloc, &cmd, gpp1);
cmd_append(&cmd, "-H"); cmd_append_alloc(alloc, &cmd, "-H");
cmd_append(&cmd, "-x"); cmd_append_alloc(alloc, &cmd, "-x");
cmd_append(&cmd, "--nostdinc"); cmd_append_alloc(alloc, &cmd, "--nostdinc");
cmd_append(&cmd, path); cmd_append_alloc(alloc, &cmd, path);
for (size_t i = 0; i < env->count; i++) { for (size_t i = 0; i < env->count; i++) {
cmd_append(&cmd, env->items[i]); cmd_append_alloc(alloc, &cmd, env->items[i]);
} }
return cmd_run_collect(&cmd, out) == 0; bool ok = cmd_run_collect_alloc(alloc, &cmd, out) == 0;
size_t len = out->count > 100 ? 100 : out->count;
LOGI("Output (first 100): \n-------------------------------\n%.*s\n-------------------------------\n", len, out->items);
return ok;
} }
#if MY_DEBUG #if MY_DEBUG
void route_build_id(struct mg_http_message *msg, Route_Result *result, void *context_data) void route_build_id(Allocator *alloc, struct mg_http_message *msg, Route_Result *result, void *context_data)
{ {
LOGI("handler build_id (%.*s)\n", msg->uri.len, msg->uri.buf);
// TODO: Somehow use our arena in here. This will require a slight rework of the way cJSON works.
cJSON *root = cJSON_CreateObject(); cJSON *root = cJSON_CreateObject();
defer { cJSON_Delete(root); } defer { cJSON_Delete(root); }
@ -85,226 +86,217 @@ void route_build_id(struct mg_http_message *msg, Route_Result *result, void *con
uchar md5_buf[16]; uchar md5_buf[16];
md5String(time, md5_buf); md5String(time, md5_buf);
String_Builder sb = {0}; String_Builder sb = {0};
defer { sb_free(&sb); }
for (size_t i = 0; i < 16; i++) { for (size_t i = 0; i < 16; i++) {
sb_append_nstr(&sb, fmt("%02x", md5_buf[i])); sb_append_nstr_alloc(alloc, &sb, clonestr_alloc(alloc, fmt("%02x", md5_buf[i])));
} }
sb_finish(&sb); sb_finish_alloc(alloc, &sb);
cJSON_AddItemToObject(root, "build_id", cJSON_CreateString(sb.items)); cJSON_AddItemToObject(root, "build_id", cJSON_CreateString(sb.items));
make_application_json(result, 200, root); make_application_json(alloc, result, 200, root);
LOGI("handler build_id done\n");
} }
#endif #endif
void route_page_not_found(struct mg_http_message *msg, Route_Result *result, void *context_data) void route_page_not_found(Allocator *alloc, struct mg_http_message *msg, Route_Result *result, void *context_data)
{ {
lock_baked_resources(); LOGI("handler page not found (%.*s)\n", msg->uri.len, msg->uri.buf);
defer { unlock_baked_resources(); }
NString_List env = {0}; NString_List env = {0};
defer { list_free(&env); }
list_append(&env, fmt("-DURL=%.*s", msg->uri.len, msg->uri.buf)); list_append_alloc(alloc, &env, clonestr_alloc(alloc, fmt("-DURL=%.*s", msg->uri.len, msg->uri.buf)));
String_Builder out = {0}; String_Builder out = {0};
defer { sb_free(&out); }
char path[PATH_MAX] = {0}; char path[PATH_MAX] = {0};
if (!get_baked_resource_path("page-missing.html", path, sizeof(path))) { if (!get_baked_resource_path("page-missing.html", path, sizeof(path))) {
make_internal_server_error(result); LOGE("Failed to get baked resource page-missing.html\n");
make_internal_server_error(alloc, result);
return; return;
} }
list_append(&env, fmt("-DCOMMIT=%s", COMMIT_STRING)); list_append_alloc(alloc, &env, clonestr_alloc(alloc, fmt("-DCOMMIT=%s", COMMIT_STRING)));
char timer[100]; char timer[100];
get_timer_string(timer, sizeof(timer)); get_timer_string(timer, sizeof(timer));
list_append(&env, fmt("-DRUNNING_SINCE=%s", timer)); list_append_alloc(alloc, &env, clonestr_alloc(alloc, fmt("-DRUNNING_SINCE=%s", timer)));
list_append(&env, fmt("-DMETA_TAGS=%s", META_TAGS)); list_append_alloc(alloc, &env, clonestr_alloc(alloc, fmt("-DMETA_TAGS=%s", META_TAGS)));
#if MY_DEBUG #if MY_DEBUG
list_append(&env, "-DHOTRELOAD"); list_append_alloc(alloc, &env, "-DHOTRELOAD");
#endif #endif
bool ok = gpp_run(path, &env, &out); bool ok = gpp_run(alloc, path, &env, &out);
sb_finish(&out); sb_finish_alloc(alloc, &out);
if (!ok) { if (!ok) {
make_internal_server_error(result); make_internal_server_error(alloc, result);
} else { } else {
make_text(result, "html", 200, out.items); make_text(alloc, result, "html", 200, out.items);
} }
LOGI("handler page not found done\n");
} }
void route_home(struct mg_http_message *msg, Route_Result *result, void *context_data) void route_home(Allocator *alloc, struct mg_http_message *msg, Route_Result *result, void *context_data)
{ {
lock_baked_resources(); LOGI("handler home (%.*s)\n", msg->uri.len, msg->uri.buf);
defer { unlock_baked_resources(); }
NString_List env = {0}; NString_List env = {0};
defer { list_free(&env); }
String_Builder out = {0}; String_Builder out = {0};
defer { sb_free(&out); }
char path[PATH_MAX] = {0}; char path[PATH_MAX] = {0};
if (!get_baked_resource_path("home.html", path, sizeof(path))) { if (!get_baked_resource_path("home.html", path, sizeof(path))) {
make_internal_server_error(result); LOGE("Failed to get baked resource home.html\n");
make_internal_server_error(alloc, result);
return; return;
} }
list_append(&env, fmt("-DCOMMIT=%s", COMMIT_STRING)); list_append_alloc(alloc, &env, clonestr_alloc(alloc, fmt("-DCOMMIT=%s", COMMIT_STRING)));
char timer[100]; char timer[100];
get_timer_string(timer, sizeof(timer)); get_timer_string(timer, sizeof(timer));
list_append(&env, fmt("-DRUNNING_SINCE=%s", timer)); list_append_alloc(alloc, &env, clonestr_alloc(alloc, fmt("-DRUNNING_SINCE=%s", timer)));
list_append(&env, fmt("-DMETA_TAGS=%s", META_TAGS)); list_append_alloc(alloc, &env, clonestr_alloc(alloc, fmt("-DMETA_TAGS=%s", META_TAGS)));
#if MY_DEBUG #if MY_DEBUG
list_append(&env, "-DHOTRELOAD"); list_append_alloc(alloc, &env, "-DHOTRELOAD");
#endif #endif
bool ok = gpp_run(path, &env, &out); bool ok = gpp_run(alloc, path, &env, &out);
sb_finish(&out); sb_finish_alloc(alloc, &out);
if (!ok) { if (!ok) {
make_internal_server_error(result); make_internal_server_error(alloc, result);
} else { } else {
make_text(result, "html", 200, out.items); make_text(alloc, result, "html", 200, out.items);
} }
LOGI("handler home done\n");
} }
void route_generic_blog(struct mg_http_message *msg, Route_Result *result, void *context_data) void route_generic_blog(Allocator *alloc, struct mg_http_message *msg, Route_Result *result, void *context_data)
{ {
lock_baked_resources(); LOGI("handler generic blog (%.*s)\n", msg->uri.len, msg->uri.buf);
defer { unlock_baked_resources(); }
NString_List env = {0}; NString_List env = {0};
defer { list_free(&env); }
String_Builder out = {0}; String_Builder out = {0};
defer { sb_free(&out); }
Baked_Resource *resource = (Baked_Resource *)context_data; Baked_Resource *resource = (Baked_Resource *)context_data;
char md_path[PATH_MAX] = {0}; char md_path[PATH_MAX] = {0};
if (!get_baked_resource_path(resource->key, md_path, sizeof(md_path))) { if (!get_baked_resource_path(resource->key, md_path, sizeof(md_path))) {
make_internal_server_error(result); LOGE("Failed to get baked resource %s\n", resource->key);
make_internal_server_error(alloc, result);
return; return;
} }
list_append(&env, fmt("-DCOMMIT=%s", COMMIT_STRING)); list_append_alloc(alloc, &env, clonestr_alloc(alloc, fmt("-DCOMMIT=%s", COMMIT_STRING)));
char timer[100]; char timer[100];
get_timer_string(timer, sizeof(timer)); get_timer_string(timer, sizeof(timer));
list_append(&env, fmt("-DRUNNING_SINCE=%s", timer)); list_append_alloc(alloc, &env, clonestr_alloc(alloc, fmt("-DRUNNING_SINCE=%s", timer)));
list_append(&env, fmt("-DMETA_TAGS=%s", META_TAGS)); list_append_alloc(alloc, &env, clonestr_alloc(alloc, fmt("-DMETA_TAGS=%s", META_TAGS)));
#if MY_DEBUG #if MY_DEBUG
list_append(&env, "-DHOTRELOAD"); list_append_alloc(alloc, &env, "-DHOTRELOAD");
#endif #endif
String_Builder md = {0}; String_Builder md = {0};
defer { sb_free(&md); } if (!sb_read_file_alloc(alloc, &md, md_path)) {
if (!sb_read_file(&md, md_path)) { make_internal_server_error(alloc, result);
make_internal_server_error(result);
return; return;
} }
String_Builder md_prepared = {0}; String_Builder md_prepared = {0};
defer { sb_free(&md_prepared); }
for (size_t i = 0; i < md.count; i++) { for (size_t i = 0; i < md.count; i++) {
char c = md.items[i]; char c = md.items[i];
switch (c) { switch (c) {
case '\"': sb_append_nstr(&md_prepared, "\\\""); break; case '\"': sb_append_nstr_alloc(alloc, &md_prepared, "\\\""); break;
case '\'': sb_append_nstr(&md_prepared, "\\\'"); break; case '\'': sb_append_nstr_alloc(alloc, &md_prepared, "\\\'"); break;
case '\\': sb_append_nstr(&md_prepared, "\\\\"); break; case '\\': sb_append_nstr_alloc(alloc, &md_prepared, "\\\\"); break;
case '\a': sb_append_nstr(&md_prepared, "\\a"); break; case '\a': sb_append_nstr_alloc(alloc, &md_prepared, "\\a"); break;
case '\b': sb_append_nstr(&md_prepared, "\\b"); break; case '\b': sb_append_nstr_alloc(alloc, &md_prepared, "\\b"); break;
case '\r': sb_append_nstr(&md_prepared, "\\r"); break; case '\r': sb_append_nstr_alloc(alloc, &md_prepared, "\\r"); break;
case '\n': sb_append_nstr(&md_prepared, "\\n"); break; case '\n': sb_append_nstr_alloc(alloc, &md_prepared, "\\n"); break;
case '\t': sb_append_nstr(&md_prepared, "\\t"); break; case '\t': sb_append_nstr_alloc(alloc, &md_prepared, "\\t"); break;
default: default:
if (iscntrl(md.items[i])) { if (iscntrl(md.items[i])) {
sb_append_nstr(&md_prepared, fmt("\\%03o", c)); sb_append_nstr_alloc(alloc, &md_prepared, clonestr_alloc(alloc, fmt("\\%03o", c)));
} else { } else {
sb_append_char(&md_prepared, c); sb_append_char_alloc(alloc, &md_prepared, c);
} }
break; break;
} }
} }
sb_finish(&md_prepared); sb_finish_alloc(alloc, &md_prepared);
list_append(&env, fmt("-DBLOG_POST_TITLE=%s", resource->key)); list_append_alloc(alloc, &env, clonestr_alloc(alloc, fmt("-DBLOG_POST_TITLE=%s", resource->key)));
list_append(&env, fmt("-DBLOG_POST_MARKDOWN=%s", md_prepared.items)); list_append_alloc(alloc, &env, clonestr_alloc(alloc, fmt("-DBLOG_POST_MARKDOWN=%s", md_prepared.items)));
char tmpl_file[PATH_MAX] = {0}; char tmpl_file[PATH_MAX] = {0};
if (!get_baked_resource_path("template-blog.html", tmpl_file, sizeof(tmpl_file))) { if (!get_baked_resource_path("template-blog.html", tmpl_file, sizeof(tmpl_file))) {
make_internal_server_error(result); make_internal_server_error(alloc, result);
return; return;
} }
bool ok = gpp_run(tmpl_file, &env, &out); bool ok = gpp_run(alloc, tmpl_file, &env, &out);
sb_finish(&out); sb_finish_alloc(alloc, &out);
if (!ok) { if (!ok) {
make_internal_server_error(result); make_internal_server_error(alloc, result);
} else { } else {
make_text(result, "html", 200, out.items); make_text(alloc, result, "html", 200, out.items);
} }
LOGI("handler generic blog done\n");
} }
void collect_blogs(Baked_Resource *resource, void *udata) void collect_blogs(Baked_Resource *resource, void *udata)
{ {
struct { Arena *tmp; String_Builder *sb; } *ud = udata; struct { Allocator *alloc; String_Builder *sb; } *ud = udata;
if ((strlen(resource->key) >= strlen("blog-")) if ((strlen(resource->key) >= strlen("blog-"))
&& strncmp(resource->key, "blog-", strlen("blog-")) == 0) { && strncmp(resource->key, "blog-", strlen("blog-")) == 0) {
sb_append_nstr_alloc(ud->tmp, ud->sb, fmt("<li><a href=\"/%s\">%s</a></li>", resource->key, resource->key)); sb_append_nstr_alloc(ud->alloc, ud->sb,
clonestr_alloc(ud->alloc, fmt("<li><a href=\"/%s\">%s</a></li>", resource->key, resource->key)));
} }
} }
void route_blog(struct mg_http_message *msg, Route_Result *result, void *context_data) void route_blog(Allocator *alloc, struct mg_http_message *msg, Route_Result *result, void *context_data)
{ {
NString_List env = {0}; NString_List env = {0};
defer { list_free(&env); }
String_Builder out = {0}; String_Builder out = {0};
defer { sb_free(&out); }
Arena tmp = arena_get();
defer { arena_destroy(&tmp); }
String_Builder sb = {0}; String_Builder sb = {0};
struct { Arena *tmp; String_Builder *sb; } collect_blogs_data = { struct { Allocator *alloc; String_Builder *sb; } collect_blogs_data = {
.tmp = &tmp, .alloc = alloc,
.sb = &sb, .sb = &sb,
}; };
baked_resource_each(&collect_blogs, &collect_blogs_data); baked_resource_each(&collect_blogs, &collect_blogs_data);
sb_finish_alloc(&tmp, &sb); sb_finish_alloc(alloc, &sb);
list_append(&env, fmt("-DBLOG_POSTS=%s", sb.items)); list_append_alloc(alloc, &env, clonestr_alloc(alloc, fmt("-DBLOG_POSTS=%s", sb.items)));
char path[PATH_MAX] = {0}; char path[PATH_MAX] = {0};
if (!get_baked_resource_path("blog.html", path, sizeof(path))) { if (!get_baked_resource_path("blog.html", path, sizeof(path))) {
make_internal_server_error(result); make_internal_server_error(alloc, result);
return; return;
} }
list_append(&env, fmt("-DCOMMIT=%s", COMMIT_STRING)); list_append_alloc(alloc, &env, clonestr_alloc(alloc, fmt("-DCOMMIT=%s", COMMIT_STRING)));
char timer[100]; char timer[100];
get_timer_string(timer, sizeof(timer)); get_timer_string(timer, sizeof(timer));
list_append(&env, fmt("-DRUNNING_SINCE=%s", timer)); list_append_alloc(alloc, &env, clonestr_alloc(alloc, fmt("-DRUNNING_SINCE=%s", timer)));
list_append(&env, fmt("-DMETA_TAGS=%s", META_TAGS)); list_append_alloc(alloc, &env, clonestr_alloc(alloc, fmt("-DMETA_TAGS=%s", META_TAGS)));
#if MY_DEBUG #if MY_DEBUG
list_append(&env, "-DHOTRELOAD"); list_append_alloc(alloc, &env, "-DHOTRELOAD");
#endif #endif
bool ok = gpp_run(path, &env, &out); bool ok = gpp_run(alloc, path, &env, &out);
sb_finish(&out); sb_finish_alloc(alloc, &out);
if (!ok) { if (!ok) {
make_internal_server_error(result); make_internal_server_error(alloc, result);
} else { } else {
make_text(result, "html", 200, out.items); make_text(alloc, result, "html", 200, out.items);
} }
} }

View File

@ -12,15 +12,17 @@ typedef struct {
int status_code; int status_code;
NString_List headers; NString_List headers;
String_Builder body; String_Builder body;
Arena *arena;
} Route_Result; } Route_Result;
typedef struct { typedef struct {
struct mg_mgr *mgr; struct mg_mgr *mgr;
ulong conn_id; ulong conn_id;
struct mg_str message; struct mg_str message;
Arena *arena;
} Route_Thread_Data; } Route_Thread_Data;
typedef void (*Route_Handler)(struct mg_http_message *msg, Route_Result *result, void *context_data); typedef void (*Route_Handler)(Allocator *alloc, struct mg_http_message *msg, Route_Result *result, void *context_data);
typedef struct { typedef struct {
char *key; // path char *key; // path
@ -29,11 +31,11 @@ typedef struct {
} Route; } Route;
#if MY_DEBUG #if MY_DEBUG
void route_build_id(struct mg_http_message *msg, Route_Result *result, void *context_data); void route_build_id(Allocator *alloc, struct mg_http_message *msg, Route_Result *result, void *context_data);
#endif #endif
void route_page_not_found(struct mg_http_message *msg, Route_Result *result, void *context_data); void route_page_not_found(Allocator *alloc, struct mg_http_message *msg, Route_Result *result, void *context_data);
void route_home(struct mg_http_message *msg, Route_Result *result, void *context_data); void route_home(Allocator *alloc, struct mg_http_message *msg, Route_Result *result, void *context_data);
void route_generic_blog(struct mg_http_message *msg, Route_Result *result, void *context_data); void route_generic_blog(Allocator *alloc, struct mg_http_message *msg, Route_Result *result, void *context_data);
void route_blog(struct mg_http_message *msg, Route_Result *result, void *context_data); void route_blog(Allocator *alloc, struct mg_http_message *msg, Route_Result *result, void *context_data);
#endif // ROUTES_H_ #endif // ROUTES_H_

View File

@ -3,7 +3,7 @@
<head> <head>
<title>Blog</title> <title>Blog</title>
<meta charset="utf-8" /> <meta charset="utf-8" />
<link rel="stylesheet" href="/etc/simple.css" /> <link rel="stylesheet" href="/bakedres/simple.css" />
<#META_TAGS> <#META_TAGS>
</head> </head>
<body> <body>
@ -24,8 +24,8 @@
</footer> </footer>
<#ifdef HOTRELOAD> <#ifdef HOTRELOAD>
<script src="/etc/hotreload.js"></script> <script src="/bakedres/hotreload.js"></script>
<#endif> <#endif>
<script src="/etc/theme.js"></script> <script src="/bakedres/theme.js"></script>
</body> </body>
</html> </html>

View File

@ -3,7 +3,7 @@
<head> <head>
<title>Kamil's personal website</title> <title>Kamil's personal website</title>
<meta charset="utf-8" /> <meta charset="utf-8" />
<link rel="stylesheet" href="/etc/simple.css" /> <link rel="stylesheet" href="/bakedres/simple.css" />
<#META_TAGS> <#META_TAGS>
</head> </head>
<body> <body>
@ -54,7 +54,7 @@
</ul> </ul>
</section> </section>
<section> <section>
<img src="/etc/me.jpg" alt="literally me" /> <img src="/bakedres/me.jpg" alt="literally me" />
</section> </section>
<footer> <footer>
<div style="float: left;"> <div style="float: left;">
@ -65,8 +65,8 @@
</footer> </footer>
<#ifdef HOTRELOAD> <#ifdef HOTRELOAD>
<script src="/etc/hotreload.js"></script> <script src="/bakedres/hotreload.js"></script>
<#endif> <#endif>
<script src="/etc/theme.js"></script> <script src="/bakedres/theme.js"></script>
</body> </body>
</html> </html>

View File

@ -2,7 +2,7 @@
<html> <html>
<head> <head>
<title>404 - Page not found</title> <title>404 - Page not found</title>
<link rel="stylesheet" href="/etc/simple.css" /> <link rel="stylesheet" href="/bakedres/simple.css" />
<#META_TAGS> <#META_TAGS>
</head> </head>
<body> <body>
@ -19,8 +19,8 @@
</footer> </footer>
<#ifdef HOTRELOAD> <#ifdef HOTRELOAD>
<script src="/etc/hotreload.js"></script> <script src="/bakedres/hotreload.js"></script>
<#endif> <#endif>
<script src="/etc/theme.js"></script> <script src="/bakedres/theme.js"></script>
</body> </body>
</html> </html>

View File

@ -3,10 +3,10 @@
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<title><#BLOG_POST_TITLE></title> <title><#BLOG_POST_TITLE></title>
<link rel="stylesheet" href="/etc/simple.css" /> <link rel="stylesheet" href="/bakedres/simple.css" />
<#META_TAGS> <#META_TAGS>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.11.1/build/styles/rainbow.min.css"> <link rel="stylesheet" href="/bakedres/hljs-rainbow.css">
<script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.11.1/build/highlight.min.js"></script> <script src="/bakedres/highlight.js"></script>
</head> </head>
<body> <body>
<div id="content"></div> <div id="content"></div>
@ -17,15 +17,15 @@
<span>Running since: <#RUNNING_SINCE></span> <span>Running since: <#RUNNING_SINCE></span>
</div> </div>
</footer> </footer>
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script> <script src="/bakedres/marked.js"></script>
<script> <script>
document.getElementById("content").innerHTML = marked.parse(`<#BLOG_POST_MARKDOWN>`); document.getElementById("content").innerHTML = marked.parse(`<#BLOG_POST_MARKDOWN>`);
</script> </script>
<#ifdef HOTRELOAD> <#ifdef HOTRELOAD>
<script src="/etc/hotreload.js"></script> <script src="/bakedres/hotreload.js"></script>
<#endif> <#endif>
<script src="/etc/theme.js"></script> <script src="/bakedres/theme.js"></script>
<script>hljs.highlightAll();</script> <script>hljs.highlightAll();</script>
</body> </body>
</html> </html>

1
zip Submodule

Submodule zip added at 649a16c5ea