Add toc; change minor things
This commit is contained in:
+270
-19
@@ -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, "</h%d>\n", level)
|
||||||
|
}
|
||||||
|
|
||||||
|
case .Paragraph:
|
||||||
|
if parent_is_li {
|
||||||
|
if event != .Enter do parent_is_li = false
|
||||||
|
} else {
|
||||||
|
if event == .Enter {
|
||||||
|
strings.write_string(sb, "<p>")
|
||||||
|
} else if event == .Exit {
|
||||||
|
strings.write_string(sb, "</p>\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case .Emph:
|
||||||
|
if event == .Enter {
|
||||||
|
strings.write_string(sb, "<em>")
|
||||||
|
} else {
|
||||||
|
strings.write_string(sb, "</em>")
|
||||||
|
}
|
||||||
|
|
||||||
|
case .Strong:
|
||||||
|
if event == .Enter {
|
||||||
|
strings.write_string(sb, "<strong>")
|
||||||
|
} else {
|
||||||
|
strings.write_string(sb, "</strong>")
|
||||||
|
}
|
||||||
|
|
||||||
|
case .Code:
|
||||||
|
strings.write_string(sb, "<code>")
|
||||||
|
lit := string(md.node_get_literal(node))
|
||||||
|
html_escape(sb, lit)
|
||||||
|
strings.write_string(sb, "</code>")
|
||||||
|
|
||||||
|
case .Text:
|
||||||
|
lit := string(md.node_get_literal(node))
|
||||||
|
if should_write_slug > 0 {
|
||||||
|
fmt.sbprintf(sb, "<h%d id='", should_write_slug)
|
||||||
|
sb_write_slug(sb, lit)
|
||||||
|
strings.write_string(sb, "'>")
|
||||||
|
should_write_slug = 0
|
||||||
|
}
|
||||||
|
html_escape(sb, lit)
|
||||||
|
|
||||||
|
case .Soft_Break:
|
||||||
|
strings.write_byte(sb, ' ')
|
||||||
|
|
||||||
|
case .Line_Break:
|
||||||
|
strings.write_string(sb, "<br />\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, "<pre class='language-")
|
||||||
|
strings.write_string(sb, lang)
|
||||||
|
strings.write_string(sb, "'><code>")
|
||||||
|
html_escape(sb, string(md.node_get_literal(node)))
|
||||||
|
strings.write_string(sb, "</code></pre>\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, "<hr />\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
case .List:
|
||||||
|
if event == .Enter {
|
||||||
|
if md.node_get_list_type(node) == .Bullet {
|
||||||
|
strings.write_string(sb, "<ul>\n")
|
||||||
|
} else {
|
||||||
|
strings.write_string(sb, "<ol>\n")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if md.node_get_list_type(node) == .Bullet {
|
||||||
|
strings.write_string(sb, "</ul>\n")
|
||||||
|
} else {
|
||||||
|
strings.write_string(sb, "</ol>\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case .Item:
|
||||||
|
if event == .Enter {
|
||||||
|
parent_is_li = true
|
||||||
|
strings.write_string(sb, "<li>")
|
||||||
|
} else {
|
||||||
|
strings.write_string(sb, "</li>\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
case .Link:
|
||||||
|
if event == .Enter {
|
||||||
|
fmt.sbprintf(sb,
|
||||||
|
"<a href=\"%s\">",
|
||||||
|
string(md.node_get_url(node)))
|
||||||
|
} else {
|
||||||
|
strings.write_string(sb, "</a>")
|
||||||
|
}
|
||||||
|
|
||||||
|
case .Image:
|
||||||
|
if event == .Enter {
|
||||||
|
fmt.sbprintf(sb,
|
||||||
|
"<img src=\"%s\" alt=\"",
|
||||||
|
string(md.node_get_url(node)))
|
||||||
|
} else {
|
||||||
|
strings.write_string(sb, "\" />")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
remove_title :: proc(root: ^md.Node) {
|
remove_title :: proc(root: ^md.Node) {
|
||||||
heading_state: enum {
|
heading_state: enum {
|
||||||
None,
|
None,
|
||||||
@@ -182,6 +327,91 @@ remove_title :: proc(root: ^md.Node) {
|
|||||||
//return title
|
//return title
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sb_indent :: proc(sb: ^strings.Builder, level: i32) {
|
||||||
|
for i in 0..<level {
|
||||||
|
strings.write_string(sb, " ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sb_write_slug :: proc(sb: ^strings.Builder, text: string) {
|
||||||
|
wrote_any := false
|
||||||
|
pending_dash := false
|
||||||
|
|
||||||
|
for c in text {
|
||||||
|
switch {
|
||||||
|
case c >= '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..<level do strings.write_string(sb, "<ol>")
|
||||||
|
started = true
|
||||||
|
} else {
|
||||||
|
for i in current_level..<level do strings.write_string(sb, "<ol>")
|
||||||
|
if current_level > level do strings.write_string(sb, "</li>")
|
||||||
|
for i in level..<current_level do strings.write_string(sb, "</ol></li>")
|
||||||
|
if current_level == level do strings.write_string(sb, "</li>")
|
||||||
|
}
|
||||||
|
|
||||||
|
strings.write_string(sb, "<li>")
|
||||||
|
current_level = level
|
||||||
|
}
|
||||||
|
case .Text:
|
||||||
|
if entered_heading {
|
||||||
|
entered_heading = false
|
||||||
|
text := string(md.node_get_literal(node))
|
||||||
|
strings.write_string(sb, "<a href='#")
|
||||||
|
sb_write_slug(sb, text)
|
||||||
|
strings.write_string(sb, "'>")
|
||||||
|
strings.write_string(sb, text)
|
||||||
|
strings.write_string(sb, "</a>")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if started {
|
||||||
|
strings.write_string(sb, "</li>")
|
||||||
|
for i in 1..<current_level do strings.write_string(sb, "</ol>")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// must free
|
// must free
|
||||||
parse_json :: proc(file_path: string) -> json.Value {
|
parse_json :: proc(file_path: string) -> json.Value {
|
||||||
json_file_content, os_err := os.read_entire_file_from_path(file_path, context.temp_allocator)
|
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},
|
{.Validate_UTF8, .Unsafe},
|
||||||
)
|
)
|
||||||
remove_title(cmark_root)
|
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_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,
|
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})
|
out_file, os_err2 := os.open(output_file_path, {.Write, .Create, .Trunc})
|
||||||
if os_err2 != nil {
|
if os_err2 != nil {
|
||||||
@@ -353,10 +598,21 @@ process_markdown :: proc(sb: ^strings.Builder, site: Page) {
|
|||||||
|
|
||||||
last_updated_date(sb, date)
|
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, "<h2>Contents</h2>")
|
||||||
|
|
||||||
|
generate_toc(sb, cmark_root)
|
||||||
|
}
|
||||||
|
strings.write_string(sb, "<hr>")
|
||||||
|
{
|
||||||
|
block_begin(sb)
|
||||||
|
defer block_end(sb)
|
||||||
|
|
||||||
|
render_html(sb, cmark_root)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
write_len: int
|
write_len: int
|
||||||
@@ -410,10 +666,11 @@ create_toc_page :: proc(title, toc_path: string, toc_info: ^[dynamic][3]string,
|
|||||||
tr_begin(sb)
|
tr_begin(sb)
|
||||||
fmt.sbprintf(
|
fmt.sbprintf(
|
||||||
sb,
|
sb,
|
||||||
"<td><a href='%s.html'>%s</a></td><td><span class='table-last-updated' data-value='%s'>aarfsdf</span></td>",
|
"<td><a href='%s'>%s</a></td><td><span class='table-last-updated' data-value='%s'>%s</span></td>",
|
||||||
item[0],
|
item[0],
|
||||||
item[1],
|
item[1],
|
||||||
item[2],
|
item[2],
|
||||||
|
item[2],
|
||||||
)
|
)
|
||||||
tr_end(sb)
|
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) {
|
last_updated_date :: proc(sb: ^strings.Builder, date: string) {
|
||||||
fmt.sbprint(sb, "<span class='last-updated'></span>")
|
strings.write_string(sb, "<span class='last-updated'>Last updated: ")
|
||||||
fmt.sbprint(
|
strings.write_string(sb, date)
|
||||||
sb,
|
strings.write_string(sb, "</span>")
|
||||||
"<script>",
|
|
||||||
"const date = new Date('",
|
|
||||||
date,
|
|
||||||
"');",
|
|
||||||
"document.querySelector('.last-updated').innerText = 'Last updated: ' + new Intl.DateTimeFormat(undefined, { dateStyle: 'long', timeStyle: 'short'}).format(date)",
|
|
||||||
"</script>",
|
|
||||||
sep = "",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
|
strings.write_string(sb, "<script>const date = new Date('")
|
||||||
|
strings.write_string(sb, date)
|
||||||
|
strings.write_string(sb, "'); document.querySelector('.last-updated').innerText = 'Last updated: ' + new Intl.DateTimeFormat(undefined, { dateStyle: 'long', timeStyle: 'short'}).format(date)</script>")
|
||||||
}
|
}
|
||||||
|
|
||||||
nav :: proc(sb: ^strings.Builder, current_path: string, site: Page) {
|
nav :: proc(sb: ^strings.Builder, current_path: string, site: Page) {
|
||||||
|
|||||||
Reference in New Issue
Block a user