Compare commits
10 Commits
64ae8365b6
...
3c1c0e412e
Author | SHA1 | Date | |
---|---|---|---|
3c1c0e412e | |||
5c125f7ca8 | |||
6cfeae7a0d | |||
4973f0c622 | |||
bcd1255965 | |||
ed1d61d976 | |||
e9a95c2c54 | |||
a90517c4da | |||
5db22711be | |||
9bb248ee03 |
1
.gitignore
vendored
1
.gitignore
vendored
@ -3,3 +3,4 @@ aboba
|
|||||||
build
|
build
|
||||||
gpp1
|
gpp1
|
||||||
watcher
|
watcher
|
||||||
|
commit.h
|
||||||
|
6
CONFIG.h
Normal file
6
CONFIG.h
Normal 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
26
baked.c
@ -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
11
baked.h
@ -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_
|
||||||
|
142
blog/curious-case-of-gebs.md
Normal file
142
blog/curious-case-of-gebs.md
Normal 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
2
blog/weird-page.md
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
# Hello??? Who's here??
|
||||||
|
|
11
blog/welcome.md
Normal file
11
blog/welcome.md
Normal 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
65
build.c
@ -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
2
gebs
Submodule gebs updated: d8e1d54f3d...6db36114d5
37
main.c
37
main.c
@ -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
161
routes.c
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
19
routes.h
19
routes.h
@ -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
13
systemd/aboba.service
Normal 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
24
timer.c
Normal 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
7
timer.h
Normal 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
29
tmpls/blog.html
Normal 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>
|
@ -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>
|
@ -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
26
tmpls/template-blog.html
Normal 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>
|
Reference in New Issue
Block a user