Skip to content

Instantly share code, notes, and snippets.

@pmttavara
Created August 13, 2022 04:06
Show Gist options
  • Save pmttavara/6afccba7af6003a19e7aed1f12472cda to your computer and use it in GitHub Desktop.
Save pmttavara/6afccba7af6003a19e7aed1f12472cda to your computer and use it in GitHub Desktop.
Static website generator in C++ used to create https://philliptrudeau.com
#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("&lt;");
} 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