mirror of
https://github.com/sbrow/thor.git
synced 2026-08-26 11:23:32 -04:00
feat: Added weight field to Page.
This commit is contained in:
@@ -485,6 +485,8 @@ These are things that are easy to get wrong:
|
||||
- **`for` each loops use `item, idx` order**, not `idx, item`. Correct: `for item, idx in arr`. Wrong: `for idx, item in arr`.
|
||||
- **`make([dynamic]T, n, allocator)` sets capacity, not length.** To get length=0 with capacity=n, use `make([dynamic]T, 0, n, allocator)`. Using `make([dynamic]T, n, allocator)` creates `len=n` with `n` zero-initialized elements.
|
||||
- `#partial switch` is usually a code smell. prefer a `case all, extra, types:` branch.
|
||||
- you don't usually need to create arena allocators in tests, instead use context.temp_allocator if you want to simplify cleanup.
|
||||
- you don't need to manually set up a tracking allocator in tests. the context.allocator will warn you about leaks.
|
||||
|
||||
## TODO
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ Page :: struct {
|
||||
description: string,
|
||||
date: string,
|
||||
year: string,
|
||||
weight: int,
|
||||
lastmod: string,
|
||||
menus: map[string]Menu_Entry,
|
||||
content: string,
|
||||
@@ -253,6 +254,7 @@ load_page :: proc(
|
||||
}
|
||||
}
|
||||
page.year = get_year(page.date)
|
||||
page.weight = fm.weight if fm.weight != 0 else DEFAULT_WEIGHT
|
||||
page.lastmod = fm.lastmod
|
||||
page.draft = fm.draft
|
||||
page.starred = fm.isStarred
|
||||
|
||||
@@ -10,6 +10,7 @@ Frontmatter :: struct {
|
||||
date: string,
|
||||
lastmod: string,
|
||||
publishDate: string,
|
||||
weight: int,
|
||||
menus: json.Value,
|
||||
layout: string,
|
||||
og: Open_Graph,
|
||||
@@ -53,6 +54,7 @@ parse_frontmatter :: proc(content: string) -> (fm: Frontmatter, body: string, ok
|
||||
fm.date = json_get_string(obj, "date")
|
||||
fm.lastmod = json_get_string(obj, "lastmod")
|
||||
fm.publishDate = json_get_string(obj, "publishDate")
|
||||
fm.weight = json_get_int(obj, "weight")
|
||||
fm.draft = json_get_bool(obj, "draft")
|
||||
fm.isStarred = json_get_bool(obj, "isStarred")
|
||||
if v, ok := obj["menus"]; ok {
|
||||
|
||||
+21
-3
@@ -166,7 +166,16 @@ merge_page_menus :: proc(site: ^Site) {
|
||||
if _, ok := page_entries[menu_name]; !ok {
|
||||
page_entries[menu_name] = make([dynamic]Menu_Entry, 0, 4, alloc)
|
||||
}
|
||||
append(&page_entries[menu_name], entry)
|
||||
// Effective weight: per-menu weight if explicit, else page.weight
|
||||
effective := entry.weight
|
||||
if effective == DEFAULT_WEIGHT {
|
||||
effective = page.weight
|
||||
}
|
||||
append(&page_entries[menu_name], Menu_Entry{
|
||||
name = entry.name,
|
||||
url = entry.url,
|
||||
weight = effective,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -212,16 +221,22 @@ collect_auto_menus :: proc(site: ^Site) {
|
||||
for section in sections {
|
||||
name := to_title_case(section, alloc)
|
||||
url := fmt.aprintf("/%s/", section, allocator = alloc)
|
||||
skip := false
|
||||
for page in site.pages {
|
||||
if page.section == section && page._is_index {
|
||||
url = page.permalink
|
||||
if page.title != "" {
|
||||
name = page.title
|
||||
}
|
||||
if _, has_main := page.menus["main"]; has_main {
|
||||
skip = true
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
append(&entries, Menu_Entry{name = name, url = url, weight = DEFAULT_WEIGHT})
|
||||
if !skip {
|
||||
append(&entries, Menu_Entry{name = name, url = url, weight = DEFAULT_WEIGHT})
|
||||
}
|
||||
}
|
||||
|
||||
// Root-level page entries (section = "", not index)
|
||||
@@ -229,9 +244,12 @@ collect_auto_menus :: proc(site: ^Site) {
|
||||
if page._is_index || page.section != "" || page.title == "" {
|
||||
continue
|
||||
}
|
||||
if _, has_main := page.menus["main"]; has_main {
|
||||
continue
|
||||
}
|
||||
append(
|
||||
&entries,
|
||||
Menu_Entry{name = page.title, url = page.permalink, weight = DEFAULT_WEIGHT},
|
||||
Menu_Entry{name = page.title, url = page.permalink, weight = page.weight},
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
+135
@@ -272,3 +272,138 @@ test_config_weight_parsing_and_sort :: proc(t: ^testing.T) {
|
||||
testing.expect_value(t, main[2].name, "Heavy")
|
||||
testing.expect_value(t, main[2].weight, 20)
|
||||
}
|
||||
|
||||
// --- json_get_int tests ---
|
||||
|
||||
@(test)
|
||||
test_json_get_int_integer :: proc(t: ^testing.T) {
|
||||
obj, _ := json.parse_string(`{"weight": 5}`, spec = .JSON)
|
||||
defer json.destroy_value(obj)
|
||||
o, _ := obj.(json.Object)
|
||||
testing.expect_value(t, json_get_int(o, "weight"), 5)
|
||||
}
|
||||
|
||||
@(test)
|
||||
test_json_get_int_float :: proc(t: ^testing.T) {
|
||||
obj, _ := json.parse_string(`{"weight": 5.0}`, spec = .JSON)
|
||||
defer json.destroy_value(obj)
|
||||
o, _ := obj.(json.Object)
|
||||
testing.expect_value(t, json_get_int(o, "weight"), 5)
|
||||
}
|
||||
|
||||
@(test)
|
||||
test_json_get_int_missing :: proc(t: ^testing.T) {
|
||||
obj, _ := json.parse_string(`{}`, spec = .JSON)
|
||||
defer json.destroy_value(obj)
|
||||
o, _ := obj.(json.Object)
|
||||
testing.expect_value(t, json_get_int(o, "weight"), 0)
|
||||
}
|
||||
|
||||
@(test)
|
||||
test_json_get_int_non_numeric :: proc(t: ^testing.T) {
|
||||
obj, _ := json.parse_string(`{"weight": "5"}`, spec = .JSON)
|
||||
defer json.destroy_value(obj)
|
||||
o, _ := obj.(json.Object)
|
||||
testing.expect_value(t, json_get_int(o, "weight"), 0)
|
||||
}
|
||||
|
||||
// --- sort_pages tests ---
|
||||
|
||||
@(test)
|
||||
test_sort_pages_weight_primary :: proc(t: ^testing.T) {
|
||||
pages := make(#soa[dynamic]Page, 0, 3)
|
||||
defer delete(pages)
|
||||
append(&pages, Page{title = "Gamma", date = "2025-01-03", weight = DEFAULT_WEIGHT})
|
||||
append(&pages, Page{title = "Alpha", date = "2025-01-01", weight = 5})
|
||||
append(&pages, Page{title = "Beta", date = "2025-01-02", weight = 1})
|
||||
|
||||
sort_pages(pages[:])
|
||||
|
||||
// weight 1, weight 5, then default weight 10
|
||||
testing.expect_value(t, pages.title[0], "Beta")
|
||||
testing.expect_value(t, pages.title[1], "Alpha")
|
||||
testing.expect_value(t, pages.title[2], "Gamma")
|
||||
}
|
||||
|
||||
@(test)
|
||||
test_sort_pages_equal_weights_by_date :: proc(t: ^testing.T) {
|
||||
pages := make(#soa[dynamic]Page, 0, 3)
|
||||
defer delete(pages)
|
||||
append(&pages, Page{title = "Old", date = "2025-01-01", weight = DEFAULT_WEIGHT})
|
||||
append(&pages, Page{title = "New", date = "2025-06-01", weight = DEFAULT_WEIGHT})
|
||||
append(&pages, Page{title = "Mid", date = "2025-03-01", weight = DEFAULT_WEIGHT})
|
||||
|
||||
sort_pages(pages[:])
|
||||
|
||||
// All same weight → date descending
|
||||
testing.expect_value(t, pages.title[0], "New")
|
||||
testing.expect_value(t, pages.title[1], "Mid")
|
||||
testing.expect_value(t, pages.title[2], "Old")
|
||||
}
|
||||
|
||||
@(test)
|
||||
test_sort_pages_mixed :: proc(t: ^testing.T) {
|
||||
pages := make(#soa[dynamic]Page, 0, 4)
|
||||
defer delete(pages)
|
||||
append(&pages, Page{title = "DefaultOld", date = "2025-01-01", weight = DEFAULT_WEIGHT})
|
||||
append(&pages, Page{title = "DefaultNew", date = "2025-06-01", weight = DEFAULT_WEIGHT})
|
||||
append(&pages, Page{title = "Heavy", date = "2025-03-01", weight = 20})
|
||||
append(&pages, Page{title = "Light", date = "2025-02-01", weight = 1})
|
||||
|
||||
sort_pages(pages[:])
|
||||
|
||||
// weight 1, weight 10 (DefaultNew by date), weight 10 (DefaultOld by date), weight 20
|
||||
testing.expect_value(t, pages.title[0], "Light")
|
||||
testing.expect_value(t, pages.title[1], "DefaultNew")
|
||||
testing.expect_value(t, pages.title[2], "DefaultOld")
|
||||
testing.expect_value(t, pages.title[3], "Heavy")
|
||||
}
|
||||
|
||||
// --- merge_page_menus effective weight tests ---
|
||||
|
||||
@(test)
|
||||
test_merge_page_menus_weight_fallback :: proc(t: ^testing.T) {
|
||||
site: Site
|
||||
mem.dynamic_arena_init(&site.arena)
|
||||
defer mem.dynamic_arena_destroy(&site.arena)
|
||||
context.allocator = site_allocator(&site)
|
||||
|
||||
page := make_page("Test", "/test/")
|
||||
page.weight = 3
|
||||
page.menus = parse_page_menus(parse_raw(`"main"`), page, site_allocator(&site))
|
||||
|
||||
site.pages = make(#soa[dynamic]Page, 0, 1, site_allocator(&site))
|
||||
append(&site.pages, page)
|
||||
|
||||
// Don't call collect_auto_menus — test merge_page_menus in isolation
|
||||
merge_page_menus(&site)
|
||||
|
||||
main, ok := site.menus["main"]
|
||||
testing.expect(t, ok)
|
||||
testing.expect(t, len(main) == 1, "expected exactly 1 entry")
|
||||
testing.expect_value(t, main[0].name, "Test")
|
||||
testing.expect_value(t, main[0].weight, 3)
|
||||
}
|
||||
|
||||
@(test)
|
||||
test_auto_menus_no_duplicate_with_frontmatter :: proc(t: ^testing.T) {
|
||||
site: Site
|
||||
mem.dynamic_arena_init(&site.arena)
|
||||
defer mem.dynamic_arena_destroy(&site.arena)
|
||||
context.allocator = site_allocator(&site)
|
||||
|
||||
// Root-level page with explicit "menus": "main"
|
||||
page := make_page("Ideas", "/ideas/")
|
||||
page.menus = parse_page_menus(parse_raw(`"main"`), page, site_allocator(&site))
|
||||
|
||||
site.pages = make(#soa[dynamic]Page, 0, 1, site_allocator(&site))
|
||||
append(&site.pages, page)
|
||||
|
||||
collect_auto_menus(&site)
|
||||
merge_page_menus(&site)
|
||||
|
||||
main, ok := site.menus["main"]
|
||||
testing.expect(t, ok)
|
||||
testing.expect(t, len(main) == 1, "expected exactly 1 entry (no duplicate)")
|
||||
testing.expect_value(t, main[0].name, "Ideas")
|
||||
}
|
||||
|
||||
+14
-3
@@ -136,7 +136,7 @@ render_template :: proc(
|
||||
render_site :: proc(site: ^Site) {
|
||||
allocator := site_allocator(site)
|
||||
pages := site.pages[:]
|
||||
sort_pages_by_date(pages)
|
||||
sort_pages(pages)
|
||||
|
||||
// Load shared resources
|
||||
partials := load_partials(&site.vfs)
|
||||
@@ -373,11 +373,12 @@ get_year :: proc(iso: string) -> string {
|
||||
return iso[:4]
|
||||
}
|
||||
|
||||
sort_pages_by_date :: proc(pages: #soa[]Page) {
|
||||
// Weight primary (ascending). Date secondary (descending) for equal weights.
|
||||
sort_pages :: proc(pages: #soa[]Page) {
|
||||
for i in 1 ..< len(pages) {
|
||||
key := pages[i]
|
||||
j := i - 1
|
||||
for j >= 0 && pages.date[j] < key.date {
|
||||
for j >= 0 && compare_pages(pages, j, key) > 0 {
|
||||
pages[j + 1] = pages[j]
|
||||
j -= 1
|
||||
}
|
||||
@@ -385,6 +386,16 @@ sort_pages_by_date :: proc(pages: #soa[]Page) {
|
||||
}
|
||||
}
|
||||
|
||||
compare_pages :: proc(pages: #soa[]Page, j: int, key: Page) -> int {
|
||||
wj := pages.weight[j]
|
||||
wk := key.weight
|
||||
if wj != wk do return wj - wk
|
||||
// Equal weight → date descending
|
||||
if pages.date[j] < key.date do return 1
|
||||
if pages.date[j] > key.date do return -1
|
||||
return 0
|
||||
}
|
||||
|
||||
write_page :: proc(output_dir: string, permalink: string, html: string) {
|
||||
rel := permalink
|
||||
if len(rel) > 0 && rel[0] == '/' {
|
||||
|
||||
Reference in New Issue
Block a user