Compare commits

..

10 Commits

Author SHA1 Message Date
3c1c0e412e Systemd service 2025-06-19 21:46:47 +02:00
5c125f7ca8 CONFIG.h 2025-06-19 15:52:47 +02:00
6cfeae7a0d Update curious case of gebs 2025-06-18 21:36:07 +02:00
4973f0c622 Different welcome page 2025-06-18 21:18:37 +02:00
bcd1255965 Finish curious case of gebs 2025-06-18 19:04:21 +02:00
ed1d61d976 Fix leaks, curious case of gebs blog post 2025-06-18 10:38:26 +02:00
e9a95c2c54 Include 'running since' in the footer 2025-06-18 02:00:52 +02:00
a90517c4da New footer that includes current commit 2025-06-18 01:51:04 +02:00
5db22711be The Blog 2025-06-18 00:44:18 +02:00
9bb248ee03 Make templates use .html extension 2025-06-16 10:25:22 +02:00
19 changed files with 555 additions and 40 deletions

1
.gitignore vendored
View File

@ -3,3 +3,4 @@ aboba
build build
gpp1 gpp1
watcher watcher
commit.h

6
CONFIG.h Normal file
View File

@ -0,0 +1,6 @@
#ifndef CONFIG_H_
#define CONFIG_H_
#define CONFIG_LISTEN_URL "http://0.0.0.0:8080"
#endif // CONFIG_H_

26
baked.c
View File

@ -8,13 +8,19 @@
INCBIN(gpp1, "./gpp1"); INCBIN(gpp1, "./gpp1");
INCBIN(home_t, "./tmpls/home.t"); INCBIN(home_html, "./tmpls/home.html");
INCBIN(page_missing_t, "./tmpls/page-missing.t"); INCBIN(page_missing_html, "./tmpls/page-missing.html");
INCBIN(template_blog_html, "./tmpls/template-blog.html");
INCBIN(blog_html, "./tmpls/blog.html");
INCBIN(simple_min_css, "./etc/simple.min.css"); INCBIN(simple_min_css, "./etc/simple.min.css");
INCBIN(favicon_ico, "./etc/favicon.ico"); INCBIN(favicon_ico, "./etc/favicon.ico");
INCBIN(hotreload_js, "./etc/hotreload.js"); INCBIN(hotreload_js, "./etc/hotreload.js");
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");
Baked_Resource *baked_resources = NULL; Baked_Resource *baked_resources = NULL;
void add_baked_resource(char *key, const uchar *data, size_t size) void add_baked_resource(char *key, const uchar *data, size_t size)
@ -30,12 +36,17 @@ void add_baked_resource(char *key, const uchar *data, size_t size)
void init_baked_resources(void) void init_baked_resources(void)
{ {
add_baked_resource("home.t", home_t_data, home_t_size); add_baked_resource("home.html", home_html_data, home_html_size);
add_baked_resource("page-missing.t", page_missing_t_data, page_missing_t_size); add_baked_resource("page-missing.html", page_missing_html_data, page_missing_html_size);
add_baked_resource("template-blog.html", template_blog_html_data, template_blog_html_size);
add_baked_resource("blog.html", blog_html_data, blog_html_size);
add_baked_resource("gpp1", gpp1_data, gpp1_size); add_baked_resource("gpp1", gpp1_data, gpp1_size);
add_baked_resource("simple.min.css", simple_min_css_data, simple_min_css_size); add_baked_resource("simple.min.css", simple_min_css_data, simple_min_css_size);
add_baked_resource("favicon.ico", favicon_ico_data, favicon_ico_size); add_baked_resource("favicon.ico", favicon_ico_data, favicon_ico_size);
add_baked_resource("hotreload.js", hotreload_js_data, hotreload_js_size); add_baked_resource("hotreload.js", hotreload_js_data, hotreload_js_size);
add_baked_resource("blog-welcome.md", blog_welcome_md_data, blog_welcome_md_size);
add_baked_resource("blog-weird-page.md", blog_weird_page_md_data, blog_weird_page_md_size);
add_baked_resource("blog-curious-case-of-gebs.md", blog_curious_case_of_gebs_md_data, blog_curious_case_of_gebs_md_size);
} }
void free_baked_resources(void) void free_baked_resources(void)
@ -56,3 +67,10 @@ bool get_baked_resource_path(char *key, char *buf, size_t size)
return false; return false;
} }
void baked_resource_each(void (*f)(Baked_Resource *resource))
{
for (size_t i = 0; i < shlen(baked_resources); i++) {
f(&baked_resources[i]);
}
}

11
baked.h
View File

@ -5,13 +5,19 @@
INCBIN_EXTERN(gpp1); INCBIN_EXTERN(gpp1);
INCBIN_EXTERN(home_t); INCBIN_EXTERN(home_html);
INCBIN_EXTERN(page_missing_t); INCBIN_EXTERN(page_missing_html);
INCBIN_EXTERN(template_blog_html);
INCBIN_EXTERN(blog_html);
INCBIN_EXTERN(simple_min_css); INCBIN_EXTERN(simple_min_css);
INCBIN_EXTERN(favicon_ico); INCBIN_EXTERN(favicon_ico);
INCBIN_EXTERN(hotreload_js); INCBIN_EXTERN(hotreload_js);
INCBIN_EXTERN(blog_welcome_md);
INCBIN_EXTERN(blog_weird_page_md);
INCBIN_EXTERN(blog_curious_case_of_gebs_md);
typedef struct { typedef struct {
char *key; // path char *key; // path
int value; // memfd int value; // memfd
@ -20,5 +26,6 @@ typedef struct {
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));
#endif // BAKED_H_ #endif // BAKED_H_

View File

@ -0,0 +1,142 @@
# GEBS - the Good Enough Build System
Source code: https://gitlab.com/kamkow1/gebs
GEBS is a reiteration of my previous build system "MIBS" (or MIni Build System). It takes some inspiration from
[Tsoding's](https://twitch.tv/tsoding) [nobuild](https://github.com/tsoding/nobuild) and later on [nob.h](https://github.com/tsoding/nob.h).
The key difference is the way GEBS is implemented on the inside, which makes it more powerful and extensible than nob.h.
GEBS also includes a bunch of extra helper macros, which turn C into a language more akin to Go or Zig, but more on that later.
## So what makes GEBS different?
### Allocators
So one thing I've noticed is that nob.h is used alongside of [arena](https://github.com/tsoding/arena). If you look into the implementation
you can see some things, which are somewhat redundant like \`arena_sprintf()\` or \`arena_da_append()\`, \`arena_sb_append_cstr()\` and so on...
First of all, why is an arena library managing string builders and dynamic arrays? In my opinion it should be the other way around.
A string builder should rather accept a generic allocator interface, which it can then utilize to get it's memory.
Basically we supplement a dynamic structure with an allocator of choice.
In GEBS this is done via a \`Gebs_Allocator\` interface.
\`\`\`c
typedef struct {
void *(*malloc)(void *self, size_t size);
void (*free)(void *self, void *memory);
void *(*realloc)(void *self, void *memory, size_t prev_size, size_t new_size);
} Gebs_Allocator;
// Wrapper macros
#define gebs_malloc(alloc, size) ((alloc)->malloc((void *)(alloc), (size)))
#define gebs_free(alloc, memory) ((alloc)->free((void *)(alloc), (memory)))
#define gebs_realloc(alloc, memory, prev_size, new_size) \
((alloc)->realloc((void *)(alloc), (memory), (prev_size), (new_size)))
\`\`\`
We then can implement an allocator that conforms to this interface and it will work with any dynamic structure.
This is my version of the \`XXX_da_append()\` macro:
\`\`\`c
#define gebs_list_append_alloc(alloc, list, item) \\
do { \\
if ((list)->items == nil) { \\
(list)->capacity = 1; \\
(list)->items = gebs_malloc((alloc), \\
sizeof(*(list)->items) * (list)->capacity); \\
} else { \\
if ((list)->count == (list)->capacity) { \\
size_t __prev_capacity = (list)->capacity; \\
(list)->capacity *= 2; \\
(list)->items = gebs_realloc((alloc), (list)->items, \\
sizeof(*(list)->items) * __prev_capacity, \\
sizeof(*(list)->items) * (list)->capacity); \\
} \\
} \\
(list)->items[(list)->count++] = (item); \\
} while(0)
#define gebs_list_append(list, item) \\
gebs_list_append_alloc(&gebs_default_allocator, (list), (item))
\`\`\`
This way a dynamic list can work with any kind of allocator - the default libc allocator, an arena or literally anything else.
We're not tied to the libc allocator and then have to implement the same macro of all other allocators.
### Defer macro
Ever forgot to place a \`free()\` call on function exit or an \`fclose()\`? The defer macro comes to the rescue. Here's a short snippet:
(Taken straight form the source code of this website btw.)
\`\`\`c
cJSON *root = cJSON_CreateObject();
defer { cJSON_Delete(root); }
char *time = __TIME__;
uchar md5_buf[16];
md5String(time, md5_buf);
String_Builder sb = {0};
defer { sb_free(&sb); }
for (size_t i = 0; i < 16; i++) {
sb_append_nstr(&sb, fmt("%02x", md5_buf[i]));
}
sb_finish(&sb);
cJSON_AddItemToObject(root, "build_id", cJSON_CreateString(sb.items));
make_application_json(result, 200, root);
\`\`\`
If not for the \`defer { ... }\` macro, remebering when to free memory would have been quite hellish.
Another example:
\`\`\`c
NString_List env = {0};
defer { list_free(&env); }
String_Builder out = {0};
defer { sb_free(&out); }
char path[PATH_MAX] = {0};
if (!get_baked_resource_path("home.html", path, sizeof(path))) {
make_internal_server_error(result);
return;
}
\`\`\`
On \`return\` we'd have to **NOT FORGET** to add \`list_free()\` and \`sb_free()\`, but now that we have our defer,
we can kind of shut the brain off and not concern ourselves with freeing the memory. We can be 100% sure it's going to
be freed if we step into the return statement.
The implementation is quite simple, actually
\`\`\`
#define defer defer__2(__COUNTER__)
#define defer__2(X) defer__3(X)
#define defer__3(X) defer__4(defer__id##X)
#define defer__4(ID) auto void ID##func(char (*)[]); __attribute__((cleanup(ID##func))) char ID##var[0]; void ID##func(char (*ID##param)[])
\`\`\`
Source article: https://gustedt.wordpress.com/2025/01/06/simple-defer-ready-to-use/
### compile_flags.txt
Clang/LLVM docs: https://clang.llvm.org/docs/JSONCompilationDatabase.html
I use clangd inside of my vim. Clangd can be configured via a json database compile_commands.json. It's quite complicated for GEBS in a sense
that it uses the \`XX.c -> XX.o\` building pattern, while GEBS is focused more on unity builds (it's on the programmer to implement caching).
Luckily, clangd can be configured via a simple and minimalistic config file - \`compile_flags.txt\`, which holds only compiler flags that
are used to compile our C files. We can for eg. put some include paths in there and clangd will pick them up.
In GEBS we can generate a \`compile_flags.txt\` file using a built-in macro:
\`\`\`c
#define CFLAGS \\
"-I.", \\
"-I./some-lib", \\
"-Wall", \\
"-Wextra" \\
// #define other stuff like CC, LDFLAGS, SOURCES
make_compile_flags(CFLAGS); // Will output the file
\`\`\`

2
blog/weird-page.md Normal file
View File

@ -0,0 +1,2 @@
# Hello??? Who's here??

11
blog/welcome.md Normal file
View File

@ -0,0 +1,11 @@
# Welcome to my blog!
## What is it about?
This blog is about me and my hobbies - recreational coding, lifting weights and other miscellaneous things.
This is pretty much a dumping ground for my thoughts, ideas, opinions and whatever I have on my mind. Don't expect
clean, structured, nice and neutral/unbiased writing. Here I can say what I want.
## Who am I?
// TODO: uhhhhh write something

65
build.c
View File

@ -16,19 +16,38 @@ int main(int argc, char ** argv)
RULE("./aboba", RULE("./aboba",
"./main.c", "./main.c",
"./routes.c", "./routes.c",
"./routes.h",
"./baked.c", "./baked.c",
"./baked.h",
"./clonestr.h",
"./commit.h",
"./timer.c",
"./timer.h",
"./CONFIG.h",
"./mongoose.o", "./mongoose.o",
"./gpp1", "./gpp1",
"./tmpls/home.t",
"./tmpls/page-missing.t", "./tmpls/home.html",
"./etc/hotreload.js" "./tmpls/page-missing.html",
"./tmpls/template-blog.html",
"./tmpls/blog.html",
"./etc/hotreload.js",
"./etc/simple.min.css",
"./blog/welcome.md",
"./blog/weird-page.md",
"./blog/curious-case-of-gebs.md"
) { ) {
RULE("./mongoose.o", "./mongoose/mongoose.c") { RULE("./mongoose.o", "./mongoose/mongoose.c") {
#if DEBUG #if DEBUG
CMD("cc", "-ggdb", "-c", "-fPIC", "-D_GNU_SOURCE", "-o", "./mongoose.o", "./mongoose/mongoose.c"); CMD("cc", "-ggdb", "-c", "-fPIC", "-Wl,-z,execstack", "-D_GNU_SOURCE",
"-o", "./mongoose.o", "./mongoose/mongoose.c");
#else #else
CMD("cc", "-c", "-fPIC", "-D_GNU_SOURCE", "-o", "./mongoose.o", "./mongoose/mongoose.c"); CMD("cc", "-c", "-fPIC", "-Wl,-z,exectack", "-D_GNU_SOURCE", "-o",
"./mongoose.o", "./mongoose/mongoose.c");
#endif #endif
} }
@ -36,16 +55,44 @@ int main(int argc, char ** argv)
CMD("cc", "-DHAVE_STRDUP", "-DHAVE_FNMATCH_H", "-o", "gpp1", "gpp/gpp.c"); CMD("cc", "-DHAVE_STRDUP", "-DHAVE_FNMATCH_H", "-o", "gpp1", "gpp/gpp.c");
} }
RULE("./commit.h") {
String_Builder commit = {0};
defer { sb_free(&commit); }
Cmd commit_get_cmd = {0};
defer { cmd_free(&commit_get_cmd); }
cmd_append(&commit_get_cmd, "git");
cmd_append(&commit_get_cmd, "rev-parse");
cmd_append(&commit_get_cmd, "HEAD");
cmd_run_collect(&commit_get_cmd, &commit);
commit.items[commit.count - 2] = '\0'; // \n -> \0
LOGI("Commit %s\n", commit.items);
String_Builder header = {0};
defer { sb_free(&header); }
sb_append_nstr(&header, "#ifndef COMMIT_H_\n");
sb_append_nstr(&header, "#define COMMIT_H_\n");
sb_append_nstr(&header, fmt("#define COMMIT_STRING \"%s\"\n", commit.items));
sb_append_nstr(&header, "#endif // COMMIT_H_\n");
FILE *out = fopen("./commit.h", "w");
if (out) {
fwrite(header.items, header.count, 1, out);
fclose(out);
}
}
#if DEBUG #if DEBUG
CMD("cc", "-fPIC", "-ggdb", "-I.", "-DDEBUG=1", "-D_GNU_SOURCE", "-DGEBS_NO_PREFIX", CMD("cc", "-fPIC", "-ggdb", "-I.", "-DDEBUG=1", "-D_GNU_SOURCE", "-DGEBS_NO_PREFIX",
"-DINCBIN_PREFIX=", "-DINCBIN_STYLE=INCBIN_STYLE_SNAKE", "-o", "./aboba", "-DINCBIN_PREFIX=", "-DINCBIN_STYLE=INCBIN_STYLE_SNAKE", "-Wl,-z,execstack", "-o", "./aboba",
"./main.c", "./routes.c", "./baked.c", "./mongoose.o", "./cJSON/cJSON.c", "./main.c", "./routes.c", "./baked.c", "./timer.c", "./mongoose.o", "./cJSON/cJSON.c",
"./md5-c/md5.c", "./md5-c/md5.c",
"-lpthread"); "-lpthread");
#else #else
CMD("cc", "-fPIC", "-I.", "-D_GNU_SOURCE", "-DGEBS_NO_PREFIX", CMD("cc", "-fPIC", "-I.", "-D_GNU_SOURCE", "-DGEBS_NO_PREFIX",
"-DINCBIN_PREFIX=", "-DINCBIN_STYLE=INCBIN_STYLE_SNAKE", "-o", "./aboba", "-DINCBIN_PREFIX=", "-DINCBIN_STYLE=INCBIN_STYLE_SNAKE", "-Wl,-z,execstack", "-o", "./aboba",
"./main.c", "./routes.c", "./baked.c", "./mongoose.o", "./cJSON/cJSON.c", "./main.c", "./routes.c", "./baked.c", "./timer.c", "./mongoose.o", "./cJSON/cJSON.c",
"./md5-c/md5.c", "./md5-c/md5.c",
"-lpthread"); "-lpthread");
#endif #endif

2
gebs

Submodule gebs updated: d8e1d54f3d...6db36114d5

37
main.c
View File

@ -9,6 +9,8 @@
#include "routes.h" #include "routes.h"
#include "baked.h" #include "baked.h"
#include "timer.h"
#include "CONFIG.h"
Route *route_hashtable = NULL; Route *route_hashtable = NULL;
@ -37,10 +39,10 @@ void *route_thread_function(void *param)
char key[MG_PATH_MAX] = {0}; char key[MG_PATH_MAX] = {0};
strncpy(key, http_msg.uri.buf, http_msg.uri.len); strncpy(key, http_msg.uri.buf, http_msg.uri.len);
Route_Handler handler = shget(route_hashtable, key); ssize_t idx = shgeti(route_hashtable, key);
Route_Result result = {0}; Route_Result result = {0};
handler(&http_msg, &result); route_hashtable[idx].value(&http_msg, &result, route_hashtable[idx].context_data);
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);
@ -91,19 +93,47 @@ void init_route_hashtable(void)
shput(route_hashtable, "/hotreload.js", &route_hotreload_js); shput(route_hashtable, "/hotreload.js", &route_hotreload_js);
shput(route_hashtable, "/", &route_home); shput(route_hashtable, "/", &route_home);
shput(route_hashtable, "/build-id", &route_build_id); shput(route_hashtable, "/build-id", &route_build_id);
shput(route_hashtable, "/blog", &route_blog);
void put_blogs(Baked_Resource *resource)
{
if ((strlen(resource->key) >= strlen("blog-"))
&& strncmp(resource->key, "blog-", strlen("blog-")) == 0) {
char *path = malloc(MG_PATH_MAX);
snprintf(path, MG_PATH_MAX, "/%s", resource->key);
shput(route_hashtable, path, &route_generic_blog);
route_hashtable[shgeti(route_hashtable, path)].context_data = (void *)resource;
}
}
baked_resource_each(&put_blogs);
}
void free_route_hashtable(void)
{
for (size_t i = 0; i < shlen(route_hashtable); i++) {
if (strlen(route_hashtable[i].key) >= strlen("blog-")
&& strncmp(route_hashtable[i].key, "blog-", strlen("blog-")) == 0) {
free(route_hashtable[i].key);
}
}
shfree(route_hashtable);
} }
int main(int argc, char ** argv) int main(int argc, char ** argv)
{ {
start_timer();
init_baked_resources(); init_baked_resources();
mg_log_set(MG_LL_DEBUG); mg_log_set(MG_LL_DEBUG);
struct mg_mgr mgr; struct mg_mgr mgr;
mg_mgr_init(&mgr); mg_mgr_init(&mgr);
init_route_hashtable(); init_route_hashtable();
defer { free_route_hashtable(); }
mg_wakeup_init(&mgr); mg_wakeup_init(&mgr);
mg_http_listen(&mgr, "http://localhost:8080", &event_handler, NULL); mg_http_listen(&mgr, CONFIG_LISTEN_URL, &event_handler, NULL);
for (;;) { for (;;) {
mg_mgr_poll(&mgr, 1000); mg_mgr_poll(&mgr, 1000);
@ -111,7 +141,6 @@ int main(int argc, char ** argv)
} }
mg_mgr_free(&mgr); mg_mgr_free(&mgr);
shfree(route_hashtable);
free_baked_resources(); free_baked_resources();

161
routes.c
View File

@ -6,6 +6,8 @@
#include "routes.h" #include "routes.h"
#include "baked.h" #include "baked.h"
#include "clonestr.h" #include "clonestr.h"
#include "commit.h"
#include "timer.h"
#define INTERNAL_SERVER_ERROR_MSG "Internal server error ;(. Try again later..." #define INTERNAL_SERVER_ERROR_MSG "Internal server error ;(. Try again later..."
@ -70,7 +72,7 @@ bool gpp_run(char *path, NString_List *env, String_Builder *out)
return cmd_run_collect(&cmd, out) == 0; return cmd_run_collect(&cmd, out) == 0;
} }
void route_build_id(struct mg_http_message *msg, Route_Result *result) ROUTE_HANDLER(build_id)
{ {
cJSON *root = cJSON_CreateObject(); cJSON *root = cJSON_CreateObject();
defer { cJSON_Delete(root); } defer { cJSON_Delete(root); }
@ -81,9 +83,7 @@ void route_build_id(struct mg_http_message *msg, Route_Result *result)
String_Builder sb = {0}; String_Builder sb = {0};
defer { sb_free(&sb); } defer { sb_free(&sb); }
for (size_t i = 0; i < 16; i++) { for (size_t i = 0; i < 16; i++) {
char tmp[3]; sb_append_nstr(&sb, fmt("%02x", md5_buf[i]));
snprintf(tmp, sizeof(tmp), "%02x", md5_buf[i]);
sb_append_nstr(&sb, tmp);
} }
sb_finish(&sb); sb_finish(&sb);
@ -92,7 +92,7 @@ void route_build_id(struct mg_http_message *msg, Route_Result *result)
make_application_json(result, 200, root); make_application_json(result, 200, root);
} }
void route_page_not_found(struct mg_http_message *msg, Route_Result *result) ROUTE_HANDLER(page_not_found)
{ {
NString_List env = {0}; NString_List env = {0};
defer { list_free(&env); } defer { list_free(&env); }
@ -103,11 +103,16 @@ void route_page_not_found(struct mg_http_message *msg, Route_Result *result)
defer { sb_free(&out); } defer { sb_free(&out); }
char path[PATH_MAX] = {0}; char path[PATH_MAX] = {0};
if (!get_baked_resource_path("page-missing.t", path, sizeof(path))) { if (!get_baked_resource_path("page-missing.html", path, sizeof(path))) {
make_internal_server_error(result); make_internal_server_error(result);
return; return;
} }
list_append(&env, fmt("-DCOMMIT=%s", COMMIT_STRING));
char timer[100];
get_timer_string(timer, sizeof(timer));
list_append(&env, fmt("-DRUNNING_SINCE=%s", timer));
#if DEBUG #if DEBUG
list_append(&env, "-DHOTRELOAD=1"); list_append(&env, "-DHOTRELOAD=1");
#else #else
@ -124,7 +129,7 @@ void route_page_not_found(struct mg_http_message *msg, Route_Result *result)
} }
} }
void route_simple_css(struct mg_http_message *msg, Route_Result *result) ROUTE_HANDLER(simple_css)
{ {
char path[PATH_MAX] = {0}; char path[PATH_MAX] = {0};
if (!get_baked_resource_path("simple.min.css", path, sizeof(path))) { if (!get_baked_resource_path("simple.min.css", path, sizeof(path))) {
@ -143,7 +148,7 @@ void route_simple_css(struct mg_http_message *msg, Route_Result *result)
make_text(result, "css", 200, sb.items); make_text(result, "css", 200, sb.items);
} }
void route_favicon(struct mg_http_message *msg, Route_Result *result) ROUTE_HANDLER(favicon)
{ {
char path[PATH_MAX] = {0}; char path[PATH_MAX] = {0};
if (!get_baked_resource_path("favicon.ico", path, sizeof(path))) { if (!get_baked_resource_path("favicon.ico", path, sizeof(path))) {
@ -162,7 +167,7 @@ void route_favicon(struct mg_http_message *msg, Route_Result *result)
make_image_xicon(result, 200, sb.items); make_image_xicon(result, 200, sb.items);
} }
void route_hotreload_js(struct mg_http_message *msg, Route_Result *result) ROUTE_HANDLER(hotreload_js)
{ {
char path[PATH_MAX] = {0}; char path[PATH_MAX] = {0};
if (!get_baked_resource_path("hotreload.js", path, sizeof(path))) { if (!get_baked_resource_path("hotreload.js", path, sizeof(path))) {
@ -181,7 +186,7 @@ void route_hotreload_js(struct mg_http_message *msg, Route_Result *result)
make_text(result, "javascript", 200, sb.items); make_text(result, "javascript", 200, sb.items);
} }
void route_home(struct mg_http_message *msg, Route_Result *result) ROUTE_HANDLER(home)
{ {
NString_List env = {0}; NString_List env = {0};
defer { list_free(&env); } defer { list_free(&env); }
@ -190,11 +195,16 @@ void route_home(struct mg_http_message *msg, Route_Result *result)
defer { sb_free(&out); } defer { sb_free(&out); }
char path[PATH_MAX] = {0}; char path[PATH_MAX] = {0};
if (!get_baked_resource_path("home.t", path, sizeof(path))) { if (!get_baked_resource_path("home.html", path, sizeof(path))) {
make_internal_server_error(result); make_internal_server_error(result);
return; return;
} }
list_append(&env, fmt("-DCOMMIT=%s", COMMIT_STRING));
char timer[100];
get_timer_string(timer, sizeof(timer));
list_append(&env, fmt("-DRUNNING_SINCE=%s", timer));
#if DEBUG #if DEBUG
list_append(&env, "-DHOTRELOAD=1"); list_append(&env, "-DHOTRELOAD=1");
#else #else
@ -210,3 +220,132 @@ void route_home(struct mg_http_message *msg, Route_Result *result)
make_text(result, "html", 200, out.items); make_text(result, "html", 200, out.items);
} }
} }
ROUTE_HANDLER(generic_blog)
{
NString_List env = {0};
defer { list_free(&env); }
String_Builder out = {0};
defer { sb_free(&out); }
Baked_Resource *resource = (Baked_Resource *)context_data;
char md_path[PATH_MAX] = {0};
if (!get_baked_resource_path(resource->key, md_path, sizeof(md_path))) {
make_internal_server_error(result);
return;
}
list_append(&env, fmt("-DCOMMIT=%s", COMMIT_STRING));
char timer[100];
get_timer_string(timer, sizeof(timer));
list_append(&env, fmt("-DRUNNING_SINCE=%s", timer));
#if DEBUG
list_append(&env, "-DHOTRELOAD=1");
#else
list_append(&env, "-DHOTRELOAD=0");
#endif
String_Builder md = {0};
defer { sb_free(&md); }
if (!sb_read_file(&md, md_path)) {
make_internal_server_error(result);
return;
}
String_Builder md_prepared = {0};
defer { sb_free(&md_prepared); }
for (size_t i = 0; i < md.count; i++) {
char c = md.items[i];
switch (c) {
case '\"': sb_append_nstr(&md_prepared, "\\\""); break;
case '\'': sb_append_nstr(&md_prepared, "\\\'"); break;
case '\\': sb_append_nstr(&md_prepared, "\\\\"); break;
case '\a': sb_append_nstr(&md_prepared, "\\a"); break;
case '\b': sb_append_nstr(&md_prepared, "\\b"); break;
case '\r': sb_append_nstr(&md_prepared, "\\r"); break;
case '\n': sb_append_nstr(&md_prepared, "\\n"); break;
case '\t': sb_append_nstr(&md_prepared, "\\t"); break;
default:
if (iscntrl(md.items[i])) {
sb_append_nstr(&md_prepared, fmt("\\%03o", c));
} else {
sb_append_char(&md_prepared, c);
}
break;
}
}
sb_finish(&md_prepared);
list_append(&env, fmt("-DBLOG_POST_TITLE=%s", resource->key));
list_append(&env, fmt("-DBLOG_POST_MARKDOWN=%s", md_prepared.items));
char tmpl_file[PATH_MAX] = {0};
if (!get_baked_resource_path("template-blog.html", tmpl_file, sizeof(tmpl_file))) {
make_internal_server_error(result);
return;
}
bool ok = gpp_run(tmpl_file, &env, &out);
sb_finish(&out);
if (!ok) {
make_internal_server_error(result);
} else {
make_text(result, "html", 200, out.items);
}
}
ROUTE_HANDLER(blog)
{
NString_List env = {0};
defer { list_free(&env); }
String_Builder out = {0};
defer { sb_free(&out); }
Arena tmp = arena_get();
defer { arena_destroy(&tmp); }
String_Builder sb = {0};
void collect_blogs(Baked_Resource *resource)
{
if ((strlen(resource->key) >= strlen("blog-"))
&& strncmp(resource->key, "blog-", strlen("blog-")) == 0) {
sb_append_nstr_alloc(&tmp, &sb, fmt("<li><a href=\"/%s\">%s</a></li>", resource->key, resource->key));
}
}
baked_resource_each(&collect_blogs);
sb_finish_alloc(&tmp, &sb);
list_append(&env, fmt("-DBLOG_POSTS=%s", sb.items));
char path[PATH_MAX] = {0};
if (!get_baked_resource_path("blog.html", path, sizeof(path))) {
make_internal_server_error(result);
return;
}
list_append(&env, fmt("-DCOMMIT=%s", COMMIT_STRING));
char timer[100];
get_timer_string(timer, sizeof(timer));
list_append(&env, fmt("-DRUNNING_SINCE=%s", timer));
#if DEBUG
list_append(&env, "-DHOTRELOAD=1");
#else
list_append(&env, "-DHOTRELOAD=0");
#endif
bool ok = gpp_run(path, &env, &out);
sb_finish(&out);
if (!ok) {
make_internal_server_error(result);
} else {
make_text(result, "html", 200, out.items);
}
}

View File

@ -16,18 +16,23 @@ typedef struct {
struct mg_str message; struct mg_str message;
} Route_Thread_Data; } Route_Thread_Data;
typedef void (*Route_Handler)(struct mg_http_message *msg, Route_Result *result); typedef void (*Route_Handler)(struct mg_http_message *msg, Route_Result *result, void *context_data);
typedef struct { typedef struct {
char *key; // path char *key; // path
Route_Handler value; Route_Handler value;
void *context_data;
} Route; } Route;
void route_page_not_found(struct mg_http_message *msg, Route_Result *result); #define ROUTE_HANDLER(name) void route_##name(struct mg_http_message *msg, Route_Result *result, void *context_data)
void route_simple_css(struct mg_http_message *msg, Route_Result *result);
void route_favicon(struct mg_http_message *msg, Route_Result *result); ROUTE_HANDLER(page_not_found);
void route_hotreload_js(struct mg_http_message *msg, Route_Result *result); ROUTE_HANDLER(simple_css);
void route_home(struct mg_http_message *msg, Route_Result *result); ROUTE_HANDLER(favicon);
void route_build_id(struct mg_http_message *msg, Route_Result *result); ROUTE_HANDLER(hotreload_js);
ROUTE_HANDLER(home);
ROUTE_HANDLER(build_id);
ROUTE_HANDLER(generic_blog);
ROUTE_HANDLER(blog);
#endif // ROUTES_H_ #endif // ROUTES_H_

13
systemd/aboba.service Normal file
View File

@ -0,0 +1,13 @@
[Unit]
Description=Aboba website server
After=network.target
[Service]
Type=simple
Restart=always
User=root
WorkingDirectory=/var/lib/aboba
ExecStart=/usr/local/bin/aboba
[Install]
WantedBy=multi-user.target

24
timer.c Normal file
View File

@ -0,0 +1,24 @@
#include <time.h>
#include <stddef.h>
#include <stdio.h>
#include "timer.h"
time_t rawtime;
void start_timer(void)
{
time(&rawtime);
}
void get_timer_string(char *output, size_t size)
{
struct tm * timeinfo;
timeinfo = localtime(&rawtime);
snprintf(output, size, "[%02d %02d %d %02d:%02d:%02d]", timeinfo->tm_mday,
timeinfo->tm_mon + 1, timeinfo->tm_year + 1900,
timeinfo->tm_hour, timeinfo->tm_min, timeinfo->tm_sec);
}

7
timer.h Normal file
View File

@ -0,0 +1,7 @@
#ifndef TIMER_H_
#define TIMER_H_
void start_timer(void);
void get_timer_string(char *output, size_t size);
#endif // TIMER_H_

29
tmpls/blog.html Normal file
View File

@ -0,0 +1,29 @@
<!DOCTYPE html>
<html>
<head>
<title>Blog</title>
<meta charset="utf-8" />
<link rel="stylesheet" href="/simple.min.css" />
</head>
<body>
<h1>Blog posts</h1>
<p>
This page aggregates all blog posts.
<a href="/blog-welcome.md">Welcome (or not) post</a>
</p>
<ul>
<#BLOG_POSTS>
</ul>
<footer>
<div style="float: left;">
<a href="/">HOME</a>
<span>COMMIT: <#COMMIT></span>
<span>Running since: <#RUNNING_SINCE></span>
</div>
</footer>
<#if HOTRELOAD>
<script src="/hotreload.js"></script>
<#endif>
</body>
</html>

View File

@ -10,6 +10,7 @@
<div style="float: left;"> <div style="float: left;">
<a class="button" href="/">home</a> <a class="button" href="/">home</a>
<a class="button" href="/#projects">projects</a> <a class="button" href="/#projects">projects</a>
<a class="button" href="/blog">blog</a>
</div> </div>
<p>Written in C™. <a href="https://gitlab.com/kamkow1/aboba" target="_blank">Check out the source code here!</a></p> <p>Written in C™. <a href="https://gitlab.com/kamkow1/aboba" target="_blank">Check out the source code here!</a></p>
<section id="projects"> <section id="projects">
@ -45,7 +46,11 @@
</ul> </ul>
</section> </section>
<footer> <footer>
<div style="float: left;">
<a href="/">HOME</a> <a href="/">HOME</a>
<span>COMMIT: <#COMMIT></span>
<span>Running since: <#RUNNING_SINCE></span>
</div>
</footer> </footer>
<#if HOTRELOAD> <#if HOTRELOAD>

View File

@ -10,7 +10,11 @@
URL was: <#URL><br /> URL was: <#URL><br />
</p> </p>
<footer> <footer>
<div style="float: left;">
<a href="/">HOME</a> <a href="/">HOME</a>
<span>COMMIT: <#COMMIT></span>
<span>Running since: <#RUNNING_SINCE></span>
</div>
</footer> </footer>
<#if HOTRELOAD> <#if HOTRELOAD>

26
tmpls/template-blog.html Normal file
View File

@ -0,0 +1,26 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title><#BLOG_POST_TITLE></title>
<link rel="stylesheet" href="/simple.min.css" />
</head>
<body>
<div id="content"></div>
<footer>
<div style="float: left;">
<a href="/">HOME</a>
<span>COMMIT: <#COMMIT></span>
<span>Running since: <#RUNNING_SINCE></span>
</div>
</footer>
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<script>
document.getElementById("content").innerHTML = marked.parse(`<#BLOG_POST_MARKDOWN>`);
</script>
<#if HOTRELOAD>
<script src="/hotreload.js"></script>
<#endif>
</body>
</html>