Mercurial
comparison mrjunejune/create_html_from_md.c @ 169:295ac2e5ec00
[MrJuneJune] Created separate target for generating html from md.
| author | MrJuneJune <me@mrjunejune.com> |
|---|---|
| date | Mon, 19 Jan 2026 17:33:18 -0800 |
| parents | |
| children |
comparison
equal
deleted
inserted
replaced
| 168:f3084bca7317 | 169:295ac2e5ec00 |
|---|---|
| 1 #include "markdown_converter/markdown_to_html.h" | |
| 2 #include "dowa/dowa.h" | |
| 3 #include <fcntl.h> | |
| 4 | |
| 5 #define BLOG_PATH "/home/june/zenbu/mrjunejune/src/blog/" | |
| 6 #define BLOG_HTML "<!DOCTYPE html>" \ | |
| 7 "<html lang=\"en\">" \ | |
| 8 "<head>" \ | |
| 9 " <meta charset=\"UTF-8\">" \ | |
| 10 " <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">" \ | |
| 11 " <meta name=\"description\" content=\"%s\">" \ | |
| 12 " <title>%s - June's Blog</title>" \ | |
| 13 " {{/parts/base_head.html}}" \ | |
| 14 " <link rel=\"stylesheet\" href=\"/public/highlight/a11y-dark.min.css\" media=\"(prefers-color-scheme: dark)\">" \ | |
| 15 " <link rel=\"stylesheet\" href=\"/public/highlight/a11y-light.min.css\" media=\"(prefers-color-scheme: light)\">" \ | |
| 16 " <script src=\"/public/highlight/highlight.min.js\"></script>" \ | |
| 17 "</head>" \ | |
| 18 "<body>" \ | |
| 19 " {{/parts/header.html}}" \ | |
| 20 " <main>" \ | |
| 21 "%s" \ | |
| 22 " </main>" \ | |
| 23 " {{/parts/footer.html}}" \ | |
| 24 " <script>hljs.highlightAll();</script>"\ | |
| 25 "</body>" | |
| 26 | |
| 27 | |
| 28 typedef struct { | |
| 29 char title[256]; | |
| 30 char description[512]; | |
| 31 char *content; // markdown content after frontmatter | |
| 32 } Blog_Metadata; | |
| 33 | |
| 34 // Parse frontmatter from markdown content | |
| 35 // Expected format: | |
| 36 // --- | |
| 37 // title: My Title | |
| 38 // description: My description | |
| 39 // --- | |
| 40 // markdown | |
| 41 static Blog_Metadata Parse_Blog_Frontmatter(const char *md_content) | |
| 42 { | |
| 43 Blog_Metadata meta = {0}; | |
| 44 strcpy(meta.title, "Untitled"); | |
| 45 strcpy(meta.description, "A blog post by June"); | |
| 46 meta.content = (char *)md_content; | |
| 47 | |
| 48 if (!md_content) return meta; | |
| 49 | |
| 50 if (strncmp(md_content, "---", 3) != 0) | |
| 51 return meta; | |
| 52 | |
| 53 const char *end_delim = strstr(md_content + 3, "\n---"); | |
| 54 if (!end_delim) | |
| 55 return meta; | |
| 56 | |
| 57 const char *cursor = md_content + 4; // skip "---\n" | |
| 58 while (cursor < end_delim) | |
| 59 { | |
| 60 // whitespaces | |
| 61 while (cursor < end_delim && (*cursor == ' ' || *cursor == '\t')) cursor++; | |
| 62 | |
| 63 const char *line_end = cursor; | |
| 64 while (line_end < end_delim && *line_end != '\n') line_end++; | |
| 65 | |
| 66 const char *colon = cursor; | |
| 67 while (colon < line_end && *colon != ':') colon++; | |
| 68 | |
| 69 if (colon < line_end) { | |
| 70 size_t key_len = colon - cursor; | |
| 71 const char *value_start = colon + 1; | |
| 72 while (value_start < line_end && (*value_start == ' ' || *value_start == '\t')) value_start++; | |
| 73 size_t value_len = line_end - value_start; | |
| 74 | |
| 75 if (key_len == 5 && strncmp(cursor, "title", 5) == 0) | |
| 76 { | |
| 77 size_t copy_len = value_len < sizeof(meta.title) - 1 ? value_len : sizeof(meta.title) - 1; | |
| 78 strncpy(meta.title, value_start, copy_len); | |
| 79 meta.title[copy_len] = '\0'; | |
| 80 } | |
| 81 else if (key_len == 11 && strncmp(cursor, "description", 11) == 0) | |
| 82 { | |
| 83 size_t copy_len = value_len < sizeof(meta.description) - 1 ? value_len : sizeof(meta.description) - 1; | |
| 84 strncpy(meta.description, value_start, copy_len); | |
| 85 meta.description[copy_len] = '\0'; | |
| 86 } | |
| 87 } | |
| 88 | |
| 89 cursor = line_end + 1; | |
| 90 } | |
| 91 | |
| 92 meta.content = (char *)(end_delim + 4); | |
| 93 if (*meta.content == '\n') meta.content++; | |
| 94 | |
| 95 return meta; | |
| 96 } | |
| 97 | |
| 98 int main() | |
| 99 { | |
| 100 Dowa_Arena *arena = Dowa_Arena_Create(1024 * 1024 * 5); | |
| 101 size_t buffer_sizes = 100 * 1024; | |
| 102 | |
| 103 char *files[] = { | |
| 104 BLOG_PATH"thoughts-on-ide/index.md", | |
| 105 BLOG_PATH"multithread-in-js/index.md", | |
| 106 BLOG_PATH"thoughts-on-tdd/index.md", | |
| 107 BLOG_PATH"my-seobeo-journey/index.md", | |
| 108 BLOG_PATH"wasm-bunny/index.md", | |
| 109 BLOG_PATH"optimizing-data-structures/index.md", | |
| 110 BLOG_PATH"websocket-demystified/index.md", | |
| 111 BLOG_PATH"optimizing-grass-rendering/index.md", | |
| 112 BLOG_PATH"wsl2-ssh/index.md" | |
| 113 }; | |
| 114 | |
| 115 for (int i = 0; i < 9; i++) | |
| 116 { | |
| 117 char *md_file_path = files[i]; | |
| 118 char *md_file = Dowa_Arena_Allocate(arena, buffer_sizes); | |
| 119 | |
| 120 FILE *file = fopen(md_file_path, "ro"); | |
| 121 fseek(file, 0, SEEK_END); | |
| 122 size_t file_length = ftell(file); | |
| 123 fseek(file, 0, SEEK_SET); | |
| 124 fread(md_file, 1, file_length, file); | |
| 125 | |
| 126 Blog_Metadata meta = Parse_Blog_Frontmatter(md_file); | |
| 127 char *md = markdown_to_html(meta.content); | |
| 128 char *ssr_body = Dowa_Arena_Allocate(arena, buffer_sizes); | |
| 129 char *final_body = Dowa_Arena_Allocate(arena, buffer_sizes); | |
| 130 snprintf(ssr_body, buffer_sizes, BLOG_HTML, meta.description, meta.title, md); | |
| 131 | |
| 132 int open_flags = O_RDWR | O_CREAT | O_EXCL; | |
| 133 size_t md_file_path_length = strlen(md_file_path); | |
| 134 md_file_path_length -= 3; // html | |
| 135 char *new_file_path = Dowa_Arena_Allocate(arena, 1024); | |
| 136 snprintf(new_file_path, 1024, "%.*s.html", (int)md_file_path_length, md_file_path); | |
| 137 | |
| 138 FILE *new_file_fd = fopen(new_file_path, "w+"); | |
| 139 if (!new_file_fd) | |
| 140 return; | |
| 141 printf("Saving to %s...\n", new_file_path); | |
| 142 fwrite(ssr_body, 1, buffer_sizes, new_file_fd); | |
| 143 fclose(new_file_fd); | |
| 144 } | |
| 145 return 0; | |
| 146 } | |
| 147 |