HTML block from a list of entries.
+// Each term and definition is rendered through cmark to process inline
+// markdown. Result lives in context.temp_allocator.
+render_deflist :: proc(entries: [dynamic]DefList_Entry) -> string {
+ sb := strings.builder_make(context.temp_allocator)
+
+ strings.write_string(&sb, "")
+ for entry in entries {
+ term_html := render_inline_md(entry.term)
+ def_html := render_inline_md(entry.definition)
+ strings.write_string(&sb, "- ")
+ strings.write_string(&sb, term_html)
+ strings.write_string(&sb, "
- ")
+ strings.write_string(&sb, def_html)
+ strings.write_string(&sb, "
")
+ }
+ strings.write_string(&sb, "
")
+
+ return strings.to_string(sb)
+}
+
+// render_inline_md renders a snippet of markdown through cmark and strips
+// the surrounding tags. Result lives in context.temp_allocator.
+render_inline_md :: proc(text: string) -> string {
+ raw := cm.markdown_to_html_from_string(text, {.Unsafe})
+ defer cm.free_string(raw)
+ return strings.clone(strip_p_tags(raw), context.temp_allocator)
+}
+
+// strip_p_tags removes surrounding
if the HTML is a single paragraph.
+strip_p_tags :: proc(html: string) -> string {
+ s := html
+ if len(s) > 0 && s[len(s) - 1] == '\n' {
+ s = s[:len(s) - 1]
+ }
+ if strings.has_prefix(s, "") && strings.has_suffix(s, "
") {
+ return s[3:len(s) - 4]
+ }
+ return s
+}
+
diff --git a/markdown/deflists_test.odin b/markdown/deflists_test.odin
new file mode 100644
index 0000000..c076b4a
--- /dev/null
+++ b/markdown/deflists_test.odin
@@ -0,0 +1,104 @@
+#+test
+package markdown
+
+import "core:strings"
+import "core:testing"
+
+@(test)
+test_single_entry :: proc(t: ^testing.T) {
+ input := "term\n\n: definition"
+ result := convert_deflists(input, context.temp_allocator)
+ expected := "- term
- definition
"
+ testing.expect_value(t, result, expected)
+}
+
+@(test)
+test_single_entry_no_blank :: proc(t: ^testing.T) {
+ input := "term\n: definition"
+ result := convert_deflists(input, context.temp_allocator)
+ expected := "- term
- definition
"
+ testing.expect_value(t, result, expected)
+}
+
+@(test)
+test_indented_variant :: proc(t: ^testing.T) {
+ input := " term\n : definition"
+ result := convert_deflists(input, context.temp_allocator)
+ expected := "- term
- definition
"
+ testing.expect_value(t, result, expected)
+}
+
+@(test)
+test_multiple_entries :: proc(t: ^testing.T) {
+ input := "t1\n\n: d1\n\nt2\n\n: d2"
+ result := convert_deflists(input, context.temp_allocator)
+ expected := "- t1
- d1
- t2
- d2
"
+ testing.expect_value(t, result, expected)
+}
+
+@(test)
+test_mixed_indented_and_non_indented :: proc(t: ^testing.T) {
+ input := "t1\n\n: d1\n\n t2\n : d2\n\nt3\n\n: d3"
+ result := convert_deflists(input, context.temp_allocator)
+ expected := "- t1
- d1
- t2
- d2
- t3
- d3
"
+ testing.expect_value(t, result, expected)
+}
+
+@(test)
+test_inline_markdown_in_term :: proc(t: ^testing.T) {
+ input := "`code`\n\n: def"
+ result := convert_deflists(input, context.temp_allocator)
+ expected := "code- def
"
+ testing.expect_value(t, result, expected)
+}
+
+@(test)
+test_inline_markdown_in_definition :: proc(t: ^testing.T) {
+ input := "term\n\n: see [Content](#content) here"
+ result := convert_deflists(input, context.temp_allocator)
+ testing.expect(t, strings.contains(result, `Content`))
+ testing.expect(t, strings.contains(result, "- see "))
+ testing.expect(t, strings.contains(result, "
"))
+}
+
+@(test)
+test_regular_text_passes_through :: proc(t: ^testing.T) {
+ input := "This is: not a deflist"
+ result := convert_deflists(input, context.temp_allocator)
+ testing.expect_value(t, result, input)
+}
+
+@(test)
+test_colon_inside_paragraph_no_false_positive :: proc(t: ^testing.T) {
+ input := "First paragraph.\n\nSecond paragraph."
+ result := convert_deflists(input, context.temp_allocator)
+ testing.expect_value(t, result, input)
+}
+
+@(test)
+test_deflist_between_paragraphs :: proc(t: ^testing.T) {
+ input := "Before.\n\nterm\n\n: def\n\nAfter."
+ result := convert_deflists(input, context.temp_allocator)
+ testing.expect(t, strings.has_prefix(result, "Before."))
+ testing.expect(t, strings.contains(result, "- term
- def
"))
+ testing.expect(t, strings.has_suffix(result, "After."))
+}
+
+@(test)
+test_empty_body :: proc(t: ^testing.T) {
+ result := convert_deflists("", context.temp_allocator)
+ testing.expect_value(t, result, "")
+}
+
+@(test)
+test_docs_md_pattern :: proc(t: ^testing.T) {
+ input := "content\n\n: `content` holds your pages.\n\n assets\n : `assets` contains files."
+ result := convert_deflists(input, context.temp_allocator)
+ testing.expect(t, strings.contains(result, ""))
+ testing.expect(t, strings.contains(result, "- content
"))
+ testing.expect(t, strings.contains(result, "- assets
"))
+ testing.expect(t, strings.contains(result, "content"))
+ testing.expect(t, strings.contains(result, "assets"))
+ testing.expect(t, strings.contains(result, "
"))
+}
+
diff --git a/markdown/footnotes.odin b/markdown/footnotes.odin
index b897259..2e37bed 100644
--- a/markdown/footnotes.odin
+++ b/markdown/footnotes.odin
@@ -1,7 +1,5 @@
package markdown
-import cm "vendor:commonmark"
-
import "core:fmt"
import "core:strings"
@@ -171,9 +169,7 @@ inject_notes :: proc(html: string, sn_defs, mn_defs: map[string]string) -> strin
}
// Render definition through cmark for markdown support
- raw_html := cm.markdown_to_html_from_string(def_text, {.Unsafe})
- defer cm.free_string(raw_html)
- def_html := strip_p_tags(raw_html)
+ def_html := render_inline_md(def_text)
note: string
defer delete(note)
@@ -200,15 +196,3 @@ inject_notes :: proc(html: string, sn_defs, mn_defs: map[string]string) -> strin
return strings.to_string(parts)
}
-// strip_p_tags removes surrounding if the HTML is a single paragraph.
-strip_p_tags :: proc(html: string) -> string {
- s := html
- if len(s) > 0 && s[len(s) - 1] == '\n' {
- s = s[:len(s) - 1]
- }
- if strings.has_prefix(s, "") && strings.has_suffix(s, "
") {
- return s[3:len(s) - 4]
- }
- return s
-}
-
diff --git a/markdown/markdown.odin b/markdown/markdown.odin
index af6e5be..ecaa3f2 100644
--- a/markdown/markdown.odin
+++ b/markdown/markdown.odin
@@ -12,9 +12,10 @@ Extension :: enum {
Highlight,
Sections,
HeadingIDs,
+ DefLists,
}
-DEFAULT_EXTENSIONS :: bit_set[Extension]{.Emoji, .Sidenotes, .Alerts, .HeadingIDs}
+DEFAULT_EXTENSIONS :: bit_set[Extension]{.Emoji, .Sidenotes, .Alerts, .HeadingIDs, .DefLists}
// Caller is responsible for freeing string
process :: proc(
@@ -29,6 +30,9 @@ process :: proc(
if .Sidenotes in ext {
clean_body, side_notes, margin_notes = strip_definitions(body)
}
+ if .DefLists in ext {
+ clean_body = convert_deflists(clean_body, allocator)
+ }
original_html := cm.markdown_to_html_from_string(clean_body, {.Unsafe})
html := strings.clone(original_html, allocator)
cm.free_string(original_html)
@@ -71,6 +75,8 @@ parse_extension_list :: proc(s: string) -> (result: bit_set[Extension]) {
result += {.Sections}
case "heading_ids":
result += {.HeadingIDs}
+ case "deflists":
+ result += {.DefLists}
}
}
return result
@@ -94,6 +100,8 @@ apply_extension_config :: proc(ext: ^bit_set[Extension], config: json.Object) {
if enabled {ext^ += {.Sections}} else {ext^ -= {.Sections}}
case "heading_ids":
if enabled {ext^ += {.HeadingIDs}} else {ext^ -= {.HeadingIDs}}
+ case "deflists":
+ if enabled {ext^ += {.DefLists}} else {ext^ -= {.DefLists}}
}
}
}
diff --git a/site/content/docs.md b/site/content/docs.md
index 4009194..5aec496 100644
--- a/site/content/docs.md
+++ b/site/content/docs.md
@@ -82,9 +82,7 @@ TODO: Describe
TODO: Don't forget to highlight differences from Hugo.
-## Templates[^tempmod]
-
-[^tempmod]: Template modification is an "advanced" feature, and shoud probably be discussed later in the page. (or possibly in the guide.)
+## Templates
Sites are built using one or more template files written in an extended version of [mustache](https://mustache.github.io) templates. The [mustache manual](https://mustache.github.io/mustache.5.html) has great explainations and a lot of examples if you want to know more, but I'll summarize them for you here.