Created
August 13, 2022 04:06
-
-
Save pmttavara/6afccba7af6003a19e7aed1f12472cda to your computer and use it in GitHub Desktop.
Static website generator in C++ used to create https://philliptrudeau.com
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <stdio.h> | |
#include <stdint.h> | |
#include <stdlib.h> | |
#include <assert.h> | |
#include <string.h> | |
#include <ctype.h> | |
#include <stdarg.h> | |
//template <class F> struct deferrer { F f; ~deferrer() { f(); } }; | |
//struct defer_dummy {}; | |
//template <class F> deferrer<F> operator*(defer_dummy, F f) { return {f}; } | |
//#define DEFER_(LINE) auto zz_defer##LINE = defer_dummy{}*[&]() | |
//#define DEFER(LINE) DEFER_(LINE) | |
//#define defer DEFER(__LINE__) | |
using s64 = int64_t; | |
using u8 = uint8_t; | |
template <class T, class U> auto min(T a, U b) { | |
return a < b ? a : b; | |
} | |
template <class T, class U, class V> auto clamp(T x, U min, V max) { | |
return x < min ? min : x > max ? max : x; | |
} | |
struct String { | |
s64 len; | |
u8 *ptr; | |
operator bool() { return len; } | |
String &operator++() { | |
len -= 1; | |
ptr += 1; | |
return *this; | |
} | |
u8 &operator[](size_t index) { | |
assert(index < len); | |
return ptr[index]; | |
} | |
String substring(s64 index) { | |
if (!*this) return {}; | |
auto real_index = clamp(index, 0, len); | |
return {real_index, ptr}; | |
} | |
String substring(s64 index, s64 length) { | |
if (!*this) return {}; | |
auto real_index = clamp(index, 0, len); | |
auto real_length = length; | |
if (real_index + length > len) real_length = len - real_index; | |
return {real_length, ptr + real_index}; | |
} | |
}; | |
s64 string_compare(String a, String b) { | |
if (!a || !b) return (bool)a - (bool)b; | |
auto cmp_len = min(a.len, b.len); | |
auto result = memcmp(a.ptr, b.ptr, (size_t)cmp_len); | |
if (result) return result; | |
return a.len - b.len; | |
} | |
bool operator<(String lhs, String rhs) { return string_compare(lhs, rhs) < 0; }; | |
bool operator>(String lhs, String rhs) { return string_compare(lhs, rhs) > 0; }; | |
bool operator<=(String lhs, String rhs) { return string_compare(lhs, rhs) <= 0; }; | |
bool operator>=(String lhs, String rhs) { return string_compare(lhs, rhs) >= 0; }; | |
bool operator==(String lhs, String rhs) { return string_compare(lhs, rhs) == 0; }; | |
bool operator!=(String lhs, String rhs) { return string_compare(lhs, rhs) != 0; }; | |
constexpr String operator""_s(const char *literal, size_t length) { | |
return {(s64)length, (u8*)literal}; | |
} | |
String split(String &s, u8 ch) { | |
while (s && s[0] == ch) ++s; | |
auto result = s; | |
while (s && s[0] != ch) ++s; | |
result.len = s.ptr - result.ptr; | |
return result; | |
} | |
String split_by_line(String &s) { | |
if (s && (s[0] == '\r' || s[0] == '\n')) { | |
if (s.len > 1 && s[0] + s[1] == '\r' + '\n') ++s;// @Attribution for addition trick goes to Sean Barrett (@nothings) | |
++s; | |
} | |
auto result = s; | |
while (s && (s[0] != '\r' && s[0] != '\n')) ++s; | |
result.len = s.ptr - result.ptr; | |
return result; | |
} | |
String read_entire_file(const char *filename) { | |
String result = {}; | |
FILE *f = fopen(filename, "rb"); | |
if (!f) return result; | |
fseek(f, 0, SEEK_END); | |
result.len = ftell(f); | |
result.ptr = (u8 *)malloc(result.len); | |
fseek(f, 0, SEEK_SET); | |
result.len = fread(result.ptr, 1, result.len, f); | |
fclose(f); | |
return result; | |
} | |
void write_entire_file(const char *filename, String file) { | |
FILE *f = fopen(filename, "wb"); | |
if (!f) return; | |
fwrite(file.ptr, 1, file.len, f); | |
fclose(f); | |
} | |
char temp[1 << 24]; | |
struct Variables { | |
String title; | |
String date_short; | |
String date_long; | |
String prev_url; | |
String next_url; | |
String prev_post; | |
String next_post; | |
}; | |
void md2html(String markdown, char *&out, Variables vars) { | |
#define Out(...) (out += snprintf(out, temp + sizeof(temp) - out, __VA_ARGS__)) | |
auto s = markdown; | |
bool in_paragraph = true; | |
bool in_pre = false; | |
bool in_code = false; | |
bool in_em = false; | |
bool in_strong = false; | |
bool in_tag = false; | |
bool in_tag_double_quotes = false; | |
bool in_tag_single_quotes = false; | |
int header_depth = 0; | |
while (s) { | |
bool should_indent = false; | |
auto line = split_by_line(s); | |
if (!in_tag) { | |
if (in_pre) { | |
if (line.substring(3) == "```"_s) { | |
Out("</pre>"); | |
line.ptr += 3; | |
line.len -= 3; | |
in_pre = false; | |
if (!line) { | |
continue; | |
} | |
} else { | |
Out("\r\n"); | |
} | |
} | |
if (!in_pre) { | |
if (!line) { | |
in_paragraph = false; | |
continue; | |
} | |
if (line.substring(4) == " "_s) { | |
in_paragraph = false; | |
should_indent = true; | |
line.ptr += 4; | |
line.len -= 4; | |
} else if (line.substring(3) == "```"_s) { | |
Out("<pre>"); | |
line.ptr += 3; | |
line.len -= 3; | |
in_pre = true; | |
if (!line) { | |
continue; | |
} | |
} else if (line[0] == '=') { | |
header_depth += 1; | |
++line; | |
while (line && line[0] == '=') { | |
header_depth += 1; | |
++line; | |
} | |
while (line && line[0] == ' ') { | |
++line; | |
} | |
Out("<h%d>", header_depth); | |
//in_paragraph = false; | |
} else { | |
//Out(" "); | |
} | |
} | |
} | |
if (!in_pre) { | |
if (in_paragraph) { | |
//if (out[-1] != '<') Out(" "); | |
} | |
//if (out[-1] != ' ') *out++ = ' '; | |
} | |
for (; line; ++line) { | |
if (in_tag) { | |
if (line[0] == '>') { | |
*out++ = '>'; | |
if (!in_tag_double_quotes && !in_tag_single_quotes) { | |
in_tag = false; | |
} | |
} else if (line[0] == '\'') { | |
*out++ = '\''; | |
if (!in_tag_double_quotes) { | |
in_tag_single_quotes = !in_tag_single_quotes; | |
} | |
} else if (line[0] == '"') { | |
*out++ = '"'; | |
if (!in_tag_single_quotes) { | |
in_tag_double_quotes = !in_tag_double_quotes; | |
} | |
} else { | |
*out++ = line[0]; | |
} | |
continue; | |
} | |
if (!in_pre && !in_tag && !in_code && !header_depth) { | |
if (!in_paragraph) { | |
in_paragraph = true; | |
should_indent = false; | |
//Out("<br>"); | |
Out("<p"); | |
if (should_indent) { | |
//Out(" class=tab"); | |
} | |
Out(">"); | |
} | |
} | |
switch (u8 c = line[0]) { | |
case '`': { | |
if (!in_pre) { | |
in_code = !in_code; | |
if (in_code) { | |
Out("<code>"); | |
} else { | |
Out("</code>"); | |
} | |
} else { | |
*out++ = '`'; | |
} | |
break; | |
} | |
case '*': { | |
if (!in_code && !in_pre) { | |
in_strong = !in_strong; | |
if (in_strong) { | |
Out("<strong>"); | |
} else { | |
Out("</strong>"); | |
} | |
} else { | |
*out++ = '*'; | |
} | |
break; | |
} | |
case '_': { | |
if (!in_code && !in_pre) { | |
in_em = !in_em; | |
if (in_em) { | |
Out("<em>"); | |
} else { | |
Out("</em>"); | |
} | |
} else { | |
*out++ = '_'; | |
} | |
break; | |
} | |
case '<': { | |
if (in_pre || in_code) { | |
Out("<"); | |
} else { | |
*out++ = '<'; | |
in_tag = true; | |
} | |
break; | |
} | |
default: { | |
bool matched = false; | |
#define MATCH(variable) \ | |
{ \ | |
String match = "$" #variable## _s; \ | |
if (line.substring(match.len) == match) { \ | |
md2html(vars.variable, out, {}); \ | |
line.ptr += match.len - 1; \ | |
line.len -= match.len - 1; \ | |
matched = true; \ | |
} \ | |
} | |
MATCH(title); | |
MATCH(date_short); | |
MATCH(date_long); | |
{ | |
String match = "$prev_next_links"_s; | |
if (line.substring(match.len) == match) { | |
bool has_prev = (vars.prev_url && vars.prev_post); | |
bool has_next = (vars.next_url && vars.next_post); | |
if (has_prev) { | |
Out("<a href="); | |
md2html(vars.prev_url, out, {}); | |
Out(">← Last post: "); | |
md2html(vars.prev_post, out, {}); | |
Out("</a>"); | |
} | |
if (has_prev && has_next) { | |
Out(" | "); | |
} | |
if (has_next) { | |
Out("<a href="); | |
md2html(vars.next_url, out, {}); | |
Out(">Next post: "); | |
md2html(vars.next_post, out, {}); | |
Out(" →</a>"); | |
} | |
line.ptr += match.len - 1; | |
line.len -= match.len - 1; | |
matched = true; | |
} | |
} | |
if (matched) { | |
// done | |
} else if (line.substring(2) == "--"_s) { | |
Out("—"); | |
++line; | |
} else { | |
if (in_pre || (!isspace(c) || !isspace(out[-1]))) { | |
*out++ = c; | |
} | |
} | |
break; | |
} | |
} | |
} | |
if (!in_tag) { | |
if (header_depth) { | |
Out("</h%d>", header_depth); | |
header_depth = 0; | |
} | |
} | |
//Out("\r\n"); | |
} | |
} | |
char *mprintf(const char *fmt, ...) { | |
va_list va; | |
va_list va2; | |
va_start(va, fmt); | |
va_copy(va2, va); | |
int count = vsnprintf(nullptr, 0, fmt, va) + 1; | |
char *result = (char *)malloc(count); | |
vsnprintf(result, count, fmt, va2); | |
va_end(va); | |
va_end(va2); | |
return result; | |
} | |
#define WIN32_LEAN_AND_MEAN | |
#define VC_EXTRALEAN | |
#include <windows.h> | |
#undef assert | |
#ifndef NDEBUG | |
#ifdef __cplusplus | |
extern "C" { | |
#endif | |
__pragma(comment(lib, "kernel32.lib")); | |
__pragma(comment(lib, "user32.lib")); | |
extern __declspec(dllimport) int __stdcall MessageBoxA(struct HWND__ *hWnd, const char *lpText, const char *lpCaption, unsigned int uType); | |
extern __declspec(dllimport) __declspec(noreturn) void __stdcall ExitProcess(unsigned int uExitCode); | |
static int assert_(const char *s) { | |
int x = MessageBoxA(0, s, "Assertion Failed", 0x2112); | |
if (x == 3) { | |
ExitProcess(1); | |
} | |
return x == 4; | |
#define assert3(LINE) #LINE | |
#define assert2(LINE) assert3(LINE) | |
#define assert(e) ((e) || assert_("At " __FILE__ ":" assert2(__LINE__) ":\n\n" #e "\n\nPress Retry to debug.") && (__debugbreak(), 0)) | |
} | |
#ifdef __cplusplus | |
} | |
#endif | |
#else | |
#define assert(e) ((void)0) | |
#endif | |
int main() { | |
auto header = read_entire_file("header.md"); | |
auto footer = read_entire_file("footer.md"); | |
auto index = read_entire_file("index.txt"); | |
auto index_header = split_by_line(index); | |
int num_posts = 0; | |
String *filenames = nullptr; | |
while (true) { | |
auto filename = split_by_line(index); | |
if (!filename) { | |
break; | |
} | |
num_posts += 1; | |
filenames = (String *)realloc(filenames, num_posts * sizeof(String)); | |
filenames[num_posts - 1] = filename; | |
} | |
String *posts = (String *)malloc(num_posts * sizeof(String)); | |
for (int i = 0; i < num_posts; i++) { | |
posts[i] = read_entire_file(mprintf("%.*s", (int)filenames[i].len, filenames[i].ptr)); | |
} | |
for (int i = 0; i < num_posts; i++) { | |
auto post = posts[i]; | |
auto out = temp; | |
Variables vars = {}; | |
vars.title = split_by_line(post); | |
vars.date_short = split_by_line(post); | |
vars.date_long = split_by_line(post); | |
if (i > 0) { | |
vars.prev_url = filenames[i - 1].substring(0, filenames[i - 1].len - 3); | |
auto prev_post = posts[i - 1]; | |
vars.prev_post = split_by_line(prev_post); | |
} | |
if (i + 1 < num_posts) { | |
vars.next_url = filenames[i + 1].substring(0, filenames[i + 1].len - 3); | |
auto next_post = posts[i + 1]; | |
vars.next_post = split_by_line(next_post); | |
} | |
md2html(header, out, vars); | |
md2html(post, out, vars); | |
md2html(footer, out, vars); | |
String file = {out - temp, (u8 *)temp}; | |
write_entire_file(mprintf("philliptrudeau.com/blog/%.*s", (int)filenames[i].len - 3, filenames[i].ptr), file); | |
} | |
{ | |
auto out = temp; | |
Variables vars = {}; | |
md2html(index_header, out, vars); | |
for (int i = 0; i < num_posts; i++) { | |
auto post = posts[i]; | |
auto title = split_by_line(post); | |
auto date_short = split_by_line(post); | |
Out("<p>"); | |
md2html(date_short, out, vars); | |
Out(" - <a href="); | |
md2html(filenames[i].substring(0, filenames[i].len - 3), out, vars); | |
Out(">"); | |
md2html(title, out, vars); | |
Out("</a>"); | |
} | |
String file = {out - temp, (u8 *)temp}; | |
write_entire_file("philliptrudeau.com/blog/index.html", file); | |
} | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment