fix: Fixed leaks.

This commit is contained in:
Spencer Brower
2026-07-16 13:41:59 -04:00
parent eb452b25fc
commit 06a3c4d82d
3 changed files with 55 additions and 19 deletions
+19 -12
View File
@@ -14,11 +14,15 @@ Note_Kind :: enum {
// sidenotes, [*id]: text for marginnotes), removes them, and returns the cleaned // sidenotes, [*id]: text for marginnotes), removes them, and returns the cleaned
// text plus separate maps of id->definition for each kind. // text plus separate maps of id->definition for each kind.
// Handles multi-line definitions with indented continuation lines. // Handles multi-line definitions with indented continuation lines.
strip_definitions :: proc(body: string) -> (clean_body: string, sn_defs, mn_defs: map[string]string) { strip_definitions :: proc(
sn_defs = make(map[string]string) body: string,
mn_defs = make(map[string]string) ) -> (
clean_body: string,
sn_defs, mn_defs: map[string]string,
) {
// TODO: Should this be temp allocated?
lines := strings.split(body, "\n") lines := strings.split(body, "\n")
defer delete(lines)
output_lines: [dynamic]string output_lines: [dynamic]string
defer delete(output_lines) defer delete(output_lines)
@@ -119,8 +123,9 @@ inject_notes :: proc(html: string, sn_defs, mn_defs: map[string]string) -> strin
return html return html
} }
parts: [dynamic]string parts: strings.Builder
defer delete(parts) strings.builder_init_len(&parts, 0) // TODO: Set a reasonable default
defer strings.builder_destroy(&parts)
remaining := html remaining := html
@@ -134,16 +139,16 @@ inject_notes :: proc(html: string, sn_defs, mn_defs: map[string]string) -> strin
pos = mn_pos pos = mn_pos
} }
if pos < 0 { if pos < 0 {
append(&parts, remaining) strings.write_string(&parts, remaining)
break break
} }
// Append text before the reference // Append text before the reference
append(&parts, remaining[:pos]) strings.write_string(&parts, remaining[:pos])
close := strings.index(remaining[pos + 2:], "]") close := strings.index(remaining[pos + 2:], "]")
if close < 0 { if close < 0 {
append(&parts, remaining[pos:]) strings.write_string(&parts, remaining[pos:])
break break
} }
@@ -157,7 +162,7 @@ inject_notes :: proc(html: string, sn_defs, mn_defs: map[string]string) -> strin
def_text, found := defs[id] def_text, found := defs[id]
if !found { if !found {
// No definition found, leave as literal text // 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:] remaining = remaining[ref_end:]
continue 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) def_html = strip_p_tags(def_html)
note: string note: string
defer delete(note)
if is_margin { if is_margin {
note = fmt.aprintf( note = fmt.aprintf(
`<label for="mn-%s" class="margin-toggle"></label><input type="checkbox" id="mn-%s" class="margin-toggle"><span class="marginnote">%s</span>`, `<label for="mn-%s" class="margin-toggle"></label><input type="checkbox" id="mn-%s" class="margin-toggle"><span class="marginnote">%s</span>`,
@@ -182,12 +188,12 @@ inject_notes :: proc(html: string, sn_defs, mn_defs: map[string]string) -> strin
def_html, def_html,
) )
} }
append(&parts, note) strings.write_string(&parts, note)
remaining = remaining[ref_end:] remaining = remaining[ref_end:]
} }
return strings.join(parts[:], "") return strings.to_string(parts)
} }
// strip_p_tags removes surrounding <p></p> if the HTML is a single paragraph. // strip_p_tags removes surrounding <p></p> if the HTML is a single paragraph.
@@ -201,3 +207,4 @@ strip_p_tags :: proc(html: string) -> string {
} }
return s return s
} }
+29 -5
View File
@@ -34,6 +34,13 @@ test_parse_def_line :: proc(t: ^testing.T) {
test_strip_definitions :: 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" body := "Intro[^a] and [*b].\n\n[^a]: a side\n\n[*b]: a margin\n"
clean, sn_defs, mn_defs := strip_definitions(body) 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 // references stay; definitions are removed
testing.expect(t, strings.contains(clean, "Intro[^a]")) testing.expect(t, strings.contains(clean, "Intro[^a]"))
@@ -60,13 +67,22 @@ test_strip_definitions :: proc(t: ^testing.T) {
@(test) @(test)
test_inject_notes :: proc(t: ^testing.T) { test_inject_notes :: proc(t: ^testing.T) {
html := "Text[^a] more [*b] end." html := "Text[^a] more [*b] end."
sn_defs := map[string]string{"a" = "side note"} sn_defs := map[string]string {
mn_defs := map[string]string{"b" = "margin note"} "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) out := inject_notes(html, sn_defs, mn_defs)
// sidenote: numbered, fn- prefix, .sidenote span, rendered text // sidenote: numbered, fn- prefix, .sidenote span, rendered text
testing.expect(t, strings.contains(out, `for="fn-a" class="margin-toggle sidenote-number"></label>`)) testing.expect(
t,
strings.contains(out, `for="fn-a" class="margin-toggle sidenote-number"></label>`),
)
testing.expect(t, strings.contains(out, `class="sidenote"`)) testing.expect(t, strings.contains(out, `class="sidenote"`))
testing.expect(t, strings.contains(out, "side note")) testing.expect(t, strings.contains(out, "side note"))
@@ -87,12 +103,20 @@ test_inject_notes_no_defs :: proc(t: ^testing.T) {
@(test) @(test)
test_inject_notes_missing_ref :: proc(t: ^testing.T) { test_inject_notes_missing_ref :: proc(t: ^testing.T) {
html := "Ref[^missing] and [*missing] end." html := "Ref[^missing] and [*missing] end."
sn := map[string]string{"other" = "x"} sn := map[string]string {
mn := map[string]string{"other2" = "y"} "other" = "x",
}
defer delete_map(sn)
mn := map[string]string {
"other2" = "y",
}
defer delete_map(mn)
out := inject_notes(html, sn, mn) out := inject_notes(html, sn, mn)
// references with no matching definition are left as literal text // 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]"))
testing.expect(t, strings.contains(out, "[*missing]")) testing.expect(t, strings.contains(out, "[*missing]"))
} }
+6 -1
View File
@@ -4,6 +4,7 @@ package main
import "core:encoding/json" import "core:encoding/json"
import "core:fmt" import "core:fmt"
import "core:log"
import "core:os" import "core:os"
import "core:testing" 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}`) path := write_temp_config("invalid", `{not valid json}`)
defer os.remove(path) 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) testing.expect(t, !ok)
} }