view mrjunejune/create_html_from_md.c @ 279:b3b547563ec7

Add Google connector service and agent wiki Implement the C/Seobeo Google Drive and Gmail connector with encrypted OAuth storage, Zenbu authentication, browser testing, AI tool discovery, chunked HTTP decoding, and Bazel coverage. Consolidate repository guidance into progressive wiki documentation and enforce arena-first allocation for new first-party C code. Co-authored-by: Copilot <[email protected]> Copilot-Session: 84c338fd-0939-4bb3-b7f3-1062eb213e5d
author MrJuneJune <me@mrjunejune.com>
date Mon, 17 Aug 2026 22:22:36 -0700
parents 5ec271d612ae
children
line wrap: on
line source

#include "markdown_converter/markdown_to_html.h"
#include "dowa/dowa.h"
#include <fcntl.h>

#define BLOG_PATH "/home/june/zenbu/mrjunejune/src/blog/"
#define BLOG_HTML "<!DOCTYPE html>" \
"<html lang=\"en\">" \
"<head>" \
"    <meta charset=\"UTF-8\">" \
"    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">" \
"    <meta name=\"description\" content=\"%s\">" \
"    <title>%s - June's Blog</title>" \
"    {{/parts/base_head.html}}" \
"    <link rel=\"stylesheet\" href=\"/public/highlight/a11y-dark.min.css\"  media=\"(prefers-color-scheme: dark)\">" \
"    <link rel=\"stylesheet\" href=\"/public/highlight/a11y-light.min.css\" media=\"(prefers-color-scheme: light)\">" \
"    <script src=\"/public/highlight/highlight.min.js\"></script>" \
"</head>" \
"<body>" \
"  {{/parts/header.html}}" \
"  <main>" \
"%s" \
"  </main>" \
"  {{/parts/footer.html}}" \
"  <script>hljs.highlightAll();</script>"\
"</body>" 


typedef struct {
  char title[256];
  char description[512];
  char *content;       // markdown content after frontmatter
} Blog_Metadata;

// Parse frontmatter from markdown content
// Expected format:
// ---
// title: My Title
// description: My description
// ---
// markdown
static Blog_Metadata Parse_Blog_Frontmatter(const char *md_content)
{
  Blog_Metadata meta = {0};
  strcpy(meta.title, "Untitled");
  strcpy(meta.description, "A blog post by June");
  meta.content = (char *)md_content;

  if (!md_content) return meta;

  if (strncmp(md_content, "---", 3) != 0)
    return meta;

  const char *end_delim = strstr(md_content + 3, "\n---");
  if (!end_delim)
    return meta;

  const char *cursor = md_content + 4; // skip "---\n"
  while (cursor < end_delim)
  {
    // whitespaces
    while (cursor < end_delim && (*cursor == ' ' || *cursor == '\t')) cursor++;

    const char *line_end = cursor;
    while (line_end < end_delim && *line_end != '\n') line_end++;

    const char *colon = cursor;
    while (colon < line_end && *colon != ':') colon++;

    if (colon < line_end) {
      size_t key_len = colon - cursor;
      const char *value_start = colon + 1;
      while (value_start < line_end && (*value_start == ' ' || *value_start == '\t')) value_start++;
      size_t value_len = line_end - value_start;

      if (key_len == 5 && strncmp(cursor, "title", 5) == 0)
      {
        size_t copy_len = value_len < sizeof(meta.title) - 1 ? value_len : sizeof(meta.title) - 1;
        strncpy(meta.title, value_start, copy_len);
        meta.title[copy_len] = '\0';
      }
      else if (key_len == 11 && strncmp(cursor, "description", 11) == 0)
      {
        size_t copy_len = value_len < sizeof(meta.description) - 1 ? value_len : sizeof(meta.description) - 1;
        strncpy(meta.description, value_start, copy_len);
        meta.description[copy_len] = '\0';
      }
    }

    cursor = line_end + 1;
  }

  meta.content = (char *)(end_delim + 4); 
  if (*meta.content == '\n') meta.content++;

  return meta;
}

int main()
{
  Dowa_Arena *arena = Dowa_Arena_Create(1024 * 1024 * 5);
  size_t buffer_sizes = 100 * 1024;

  char *files[] = {
    BLOG_PATH"thoughts-on-ide/index.md",
    BLOG_PATH"multithread-in-js/index.md",
    BLOG_PATH"thoughts-on-tdd/index.md",
    BLOG_PATH"my-seobeo-journey/index.md",
    BLOG_PATH"wasm-bunny/index.md",
    BLOG_PATH"optimizing-data-structures/index.md",
    BLOG_PATH"websocket-demystified/index.md",
    BLOG_PATH"optimizing-grass-rendering/index.md",
    BLOG_PATH"wsl2-ssh/index.md"
  };

  for (int i = 0; i < 9; i++)
  {
    char *md_file_path = files[i];
    char *md_file = Dowa_Arena_Allocate(arena, buffer_sizes);

    FILE *file = fopen(md_file_path, "ro");
    fseek(file, 0, SEEK_END);
    size_t file_length = ftell(file);
    fseek(file, 0, SEEK_SET);
    fread(md_file, 1, file_length, file);

    Blog_Metadata meta = Parse_Blog_Frontmatter(md_file);
    char *md = markdown_to_html(meta.content);
    char *ssr_body = Dowa_Arena_Allocate(arena, buffer_sizes);
    snprintf(ssr_body, buffer_sizes, BLOG_HTML, meta.description, meta.title, md);
    markdown_free(md);

    size_t md_file_path_length = strlen(md_file_path);
    md_file_path_length -= 3; // html
    char *new_file_path = Dowa_Arena_Allocate(arena, 1024);
    snprintf(new_file_path, 1024, "%.*s.html", (int)md_file_path_length, md_file_path);

    FILE *new_file_fd = fopen(new_file_path, "w+");
    if (!new_file_fd)
    {
      Dowa_Arena_Free(arena);
      return 1;
    }
    printf("Saving to %s...\n", new_file_path);
    fwrite(ssr_body, 1, buffer_sizes, new_file_fd);
    fclose(new_file_fd);
  }
  Dowa_Arena_Free(arena);
  return 0;
}