diff --git a/footnotes.odin b/footnotes.odin index 85b6fb4..4d7c5cd 100644 --- a/footnotes.odin +++ b/footnotes.odin @@ -14,11 +14,15 @@ Note_Kind :: enum { // sidenotes, [*id]: text for marginnotes), removes them, and returns the cleaned // text plus separate maps of id->definition for each kind. // Handles multi-line definitions with indented continuation lines. -strip_definitions :: proc(body: string) -> (clean_body: string, sn_defs, mn_defs: map[string]string) { - sn_defs = make(map[string]string) - mn_defs = make(map[string]string) - +strip_definitions :: proc( + body: string, +) -> ( + clean_body: string, + sn_defs, mn_defs: map[string]string, +) { + // TODO: Should this be temp allocated? lines := strings.split(body, "\n") + defer delete(lines) output_lines: [dynamic]string defer delete(output_lines) @@ -119,8 +123,9 @@ inject_notes :: proc(html: string, sn_defs, mn_defs: map[string]string) -> strin return html } - parts: [dynamic]string - defer delete(parts) + parts: strings.Builder + strings.builder_init_len(&parts, 0) // TODO: Set a reasonable default + defer strings.builder_destroy(&parts) remaining := html @@ -134,20 +139,20 @@ inject_notes :: proc(html: string, sn_defs, mn_defs: map[string]string) -> strin pos = mn_pos } if pos < 0 { - append(&parts, remaining) + strings.write_string(&parts, remaining) break } // Append text before the reference - append(&parts, remaining[:pos]) + strings.write_string(&parts, remaining[:pos]) close := strings.index(remaining[pos + 2:], "]") if close < 0 { - append(&parts, remaining[pos:]) + strings.write_string(&parts, remaining[pos:]) break } - id := remaining[pos + 2 : pos + 2 + close] + id := remaining[pos + 2:pos + 2 + close] ref_end := pos + 2 + close + 1 defs := sn_defs @@ -157,7 +162,7 @@ inject_notes :: proc(html: string, sn_defs, mn_defs: map[string]string) -> strin def_text, found := defs[id] if !found { // No definition found, leave as literal text - append(&parts, remaining[pos:ref_end]) + strings.write_string(&parts, remaining[pos:ref_end]) remaining = remaining[ref_end:] continue } @@ -167,6 +172,7 @@ inject_notes :: proc(html: string, sn_defs, mn_defs: map[string]string) -> strin def_html = strip_p_tags(def_html) note: string + defer delete(note) if is_margin { note = fmt.aprintf( `%s`, @@ -182,12 +188,12 @@ inject_notes :: proc(html: string, sn_defs, mn_defs: map[string]string) -> strin def_html, ) } - append(&parts, note) + strings.write_string(&parts, note) remaining = remaining[ref_end:] } - return strings.join(parts[:], "") + return strings.to_string(parts) } // strip_p_tags removes surrounding
if the HTML is a single paragraph. @@ -201,3 +207,4 @@ strip_p_tags :: proc(html: string) -> string { } return s } + diff --git a/footnotes_test.odin b/footnotes_test.odin index 40ff4bd..811a678 100644 --- a/footnotes_test.odin +++ b/footnotes_test.odin @@ -34,6 +34,13 @@ test_parse_def_line :: proc(t: ^testing.T) { test_strip_definitions :: proc(t: ^testing.T) { body := "Intro[^a] and [*b].\n\n[^a]: a side\n\n[*b]: a margin\n" clean, sn_defs, mn_defs := strip_definitions(body) + defer { + delete(clean) + delete(sn_defs["a"]) + delete(sn_defs) + delete(mn_defs["b"]) + delete(mn_defs) + } // references stay; definitions are removed testing.expect(t, strings.contains(clean, "Intro[^a]")) @@ -60,13 +67,22 @@ test_strip_definitions :: proc(t: ^testing.T) { @(test) test_inject_notes :: proc(t: ^testing.T) { html := "Text[^a] more [*b] end." - sn_defs := map[string]string{"a" = "side note"} - mn_defs := map[string]string{"b" = "margin note"} + sn_defs := map[string]string { + "a" = "side note", + } + defer delete(sn_defs) + mn_defs := map[string]string { + "b" = "margin note", + } + defer delete(mn_defs) out := inject_notes(html, sn_defs, mn_defs) // sidenote: numbered, fn- prefix, .sidenote span, rendered text - testing.expect(t, strings.contains(out, `for="fn-a" class="margin-toggle sidenote-number">`)) + testing.expect( + t, + strings.contains(out, `for="fn-a" class="margin-toggle sidenote-number">`), + ) testing.expect(t, strings.contains(out, `class="sidenote"`)) testing.expect(t, strings.contains(out, "side note")) @@ -87,12 +103,20 @@ test_inject_notes_no_defs :: proc(t: ^testing.T) { @(test) test_inject_notes_missing_ref :: proc(t: ^testing.T) { html := "Ref[^missing] and [*missing] end." - sn := map[string]string{"other" = "x"} - mn := map[string]string{"other2" = "y"} + sn := map[string]string { + "other" = "x", + } + defer delete_map(sn) + mn := map[string]string { + "other2" = "y", + } + defer delete_map(mn) out := inject_notes(html, sn, mn) // references with no matching definition are left as literal text + testing.expect(t, len(out) > 2) testing.expect(t, strings.contains(out, "[^missing]")) testing.expect(t, strings.contains(out, "[*missing]")) } + diff --git a/site_test.odin b/site_test.odin index 55cd900..e8bdd69 100644 --- a/site_test.odin +++ b/site_test.odin @@ -4,6 +4,7 @@ package main import "core:encoding/json" import "core:fmt" +import "core:log" import "core:os" import "core:testing" @@ -71,7 +72,11 @@ test_load_site_config_invalid_json :: proc(t: ^testing.T) { path := write_temp_config("invalid", `{not valid json}`) defer os.remove(path) - _, ok := load_site_config(path, context.temp_allocator) + ok := false + { + context.logger = log.nil_logger() + _, ok = load_site_config(path, context.temp_allocator) + } testing.expect(t, !ok) }