From b5f3ed49d33136276a30650c6157ea3e74580f36 Mon Sep 17 00:00:00 2001 From: "Paul W." Date: Wed, 5 Aug 2026 18:37:16 -0400 Subject: [PATCH] Add toc; change minor things --- generator.odin | 289 +++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 270 insertions(+), 19 deletions(-) diff --git a/generator.odin b/generator.odin index d711b46..3420b96 100644 --- a/generator.odin +++ b/generator.odin @@ -146,6 +146,151 @@ create_output_directories :: proc(dir_names: []string) { } } +html_escape :: proc(sb: ^strings.Builder, html: string) { + strings.write_string(sb, html) +} + +render_html :: proc(sb: ^strings.Builder, root: ^md.Node) { + iter := md.iter_new(root) + defer md.iter_free(iter) + should_write_slug: i32 = 0 + parent_is_li := false + + for md.iter_next(iter) != .Done { + node := md.iter_get_node(iter) + event := md.iter_get_event_type(iter) + + #partial switch node.type { + case .Heading: + level := md.node_get_heading_level(node) + + if event == .Enter { + should_write_slug = level + } else { + fmt.sbprintf(sb, "\n", level) + } + + case .Paragraph: + if parent_is_li { + if event != .Enter do parent_is_li = false + } else { + if event == .Enter { + strings.write_string(sb, "

") + } else if event == .Exit { + strings.write_string(sb, "

\n") + } + } + + case .Emph: + if event == .Enter { + strings.write_string(sb, "") + } else { + strings.write_string(sb, "") + } + + case .Strong: + if event == .Enter { + strings.write_string(sb, "") + } else { + strings.write_string(sb, "") + } + + case .Code: + strings.write_string(sb, "") + lit := string(md.node_get_literal(node)) + html_escape(sb, lit) + strings.write_string(sb, "") + + case .Text: + lit := string(md.node_get_literal(node)) + if should_write_slug > 0 { + fmt.sbprintf(sb, "") + should_write_slug = 0 + } + html_escape(sb, lit) + + case .Soft_Break: + strings.write_byte(sb, ' ') + + case .Line_Break: + strings.write_string(sb, "
\n") + + case .Code_Block: + fence_info := string(md.node_get_fence_info(node)) + i := strings.index_any(fence_info, " \t\r\n") + + lang := fence_info + if i != -1 { + lang = fence_info[:i] + } + + strings.write_string(sb, "
")
+            html_escape(sb, string(md.node_get_literal(node)))
+            strings.write_string(sb, "
\n") + + case .HTML_Inline: + if event == .Enter { + strings.write_string(sb, string(md.node_get_literal(node))) + } + + case .HTML_Block: + if event == .Enter { + strings.write_string(sb, string(md.node_get_literal(node))) + } + + case .Thematic_Break: + if event == .Enter { + strings.write_string(sb, "
\n") + } + + case .List: + if event == .Enter { + if md.node_get_list_type(node) == .Bullet { + strings.write_string(sb, "\n") + } else { + strings.write_string(sb, "\n") + } + } + + case .Item: + if event == .Enter { + parent_is_li = true + strings.write_string(sb, "
  • ") + } else { + strings.write_string(sb, "
  • \n") + } + + case .Link: + if event == .Enter { + fmt.sbprintf(sb, + "", + string(md.node_get_url(node))) + } else { + strings.write_string(sb, "") + } + + case .Image: + if event == .Enter { + fmt.sbprintf(sb, + "\"",") + } + } + } +} + remove_title :: proc(root: ^md.Node) { heading_state: enum { None, @@ -182,6 +327,91 @@ remove_title :: proc(root: ^md.Node) { //return title } +sb_indent :: proc(sb: ^strings.Builder, level: i32) { + for i in 0..= 'A' && c <= 'Z': + if pending_dash { + strings.write_byte(sb, '-') + pending_dash = false + } + strings.write_byte(sb, byte(c + ('a' - 'A'))) + wrote_any = true + + case (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9'): + if pending_dash { + strings.write_byte(sb, '-') + pending_dash = false + } + strings.write_byte(sb, byte(c)) + wrote_any = true + + case: + if wrote_any { + pending_dash = true + } + } + } +} + +generate_toc :: proc(sb: ^strings.Builder, root: ^md.Node) { + entered_heading := false + started := false + iter := md.iter_new(root) + defer md.iter_free(iter) + current_level: i32 = 0 + for md.iter_next(iter) != .Done { + event_type := md.iter_get_event_type(iter) + node := md.iter_get_node(iter) + + #partial switch node.type { + case .Heading: + level := md.node_get_heading_level(node) + event_type := md.iter_get_event_type(iter) + + if event_type == .Enter { + entered_heading = true + if !started { + for i in 1..") + started = true + } else { + for i in current_level..") + if current_level > level do strings.write_string(sb, "") + for i in level..") + if current_level == level do strings.write_string(sb, "") + } + + strings.write_string(sb, "
  • ") + current_level = level + } + case .Text: + if entered_heading { + entered_heading = false + text := string(md.node_get_literal(node)) + strings.write_string(sb, "") + strings.write_string(sb, text) + strings.write_string(sb, "") + } + } + } + + if started { + strings.write_string(sb, "
  • ") + for i in 1..") + } +} + // must free parse_json :: proc(file_path: string) -> json.Value { json_file_content, os_err := os.read_entire_file_from_path(file_path, context.temp_allocator) @@ -326,13 +556,28 @@ process_markdown :: proc(sb: ^strings.Builder, site: Page) { {.Validate_UTF8, .Unsafe}, ) remove_title(cmark_root) - html := md.render_html(cmark_root, {.Unsafe}) - output_file_name := fmt.tprint(md_name, ".html", sep = "") + + dir, alloc_err3 := os.join_path( + {OUTPUT_ROOT_NAME, markdown_dir, md_name}, + context.temp_allocator + ) + if alloc_err3 != nil { + fmt.println("allocator error:", alloc_err3) + continue + } + os.make_directory(dir) + output_file_path, alloc_err2 := os.join_path( - {OUTPUT_ROOT_NAME, markdown_dir, output_file_name}, + {OUTPUT_ROOT_NAME, markdown_dir, md_name, "index.html"}, context.temp_allocator, ) + + if alloc_err2 != nil { + fmt.println("allocator error:", alloc_err3) + continue + } + out_file, os_err2 := os.open(output_file_path, {.Write, .Create, .Trunc}) if os_err2 != nil { @@ -353,10 +598,21 @@ process_markdown :: proc(sb: ^strings.Builder, site: Page) { last_updated_date(sb, date) - block_begin(sb) - defer block_end(sb) + { + block_begin(sb) + defer block_end(sb) - fmt.sbprint(sb, html) + strings.write_string(sb, "

    Contents

    ") + + generate_toc(sb, cmark_root) + } + strings.write_string(sb, "
    ") + { + block_begin(sb) + defer block_end(sb) + + render_html(sb, cmark_root) + } } write_len: int @@ -410,10 +666,11 @@ create_toc_page :: proc(title, toc_path: string, toc_info: ^[dynamic][3]string, tr_begin(sb) fmt.sbprintf( sb, - "%saarfsdf", + "%s%s", item[0], item[1], item[2], + item[2], ) tr_end(sb) } @@ -457,19 +714,13 @@ create_toc_page :: proc(title, toc_path: string, toc_info: ^[dynamic][3]string, } last_updated_date :: proc(sb: ^strings.Builder, date: string) { - fmt.sbprint(sb, "") - fmt.sbprint( - sb, - "", - sep = "", - ) - + strings.write_string(sb, "Last updated: ") + strings.write_string(sb, date) + strings.write_string(sb, "") + strings.write_string(sb, "") } nav :: proc(sb: ^strings.Builder, current_path: string, site: Page) {