diff --git a/AGENTS.md b/AGENTS.md index 9716b41..c2a3e14 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -220,7 +220,7 @@ Templates use Mustache with template inheritance (`{{ {{

{{page_title}}

{{&content}}
+

{{page.title}}

{{&content}}
{{/main}} {{/base}} ``` @@ -240,7 +240,7 @@ Base_Data :: struct { } Page_Data :: struct { using base: Base_Data, // fields promoted via reflection fallback - page_title: string, + page.title: string, date: string, // raw ISO 8601; formatted via `| format` in templates } Home_Data :: struct { @@ -249,7 +249,7 @@ Home_Data :: struct { } Section_Data :: struct { using base: Base_Data, - page_title: string, + page.title: string, posts: [dynamic]Page_Context, // flat list; year grouping done in template via pipe } ``` diff --git a/TODOS.md b/TODOS.md index fc990ee..e2a876a 100644 --- a/TODOS.md +++ b/TODOS.md @@ -8,6 +8,8 @@ - [ ] consider adding a limit to the context stack in mustache. - [ ] better diagnostics for syntax errors in treesitter. - [x] Add heading ids as a default on extension. +- [ ] show "stack traces" in template error diagnostics +- [ ] starred must be a param. ## Performance @@ -32,6 +34,11 @@ - [ ] can markdown extensions run in parallel? - [ ] enforce MAX_SLUG_LENGTH +## Remove Privileged content + +- [ ] `group_by` currently requires a computed `year` field on the page. + - We should replace this with `{{ pages | group_by (date | "2006") }}` or similar + ## Memory Management @@ -145,6 +152,7 @@ main :: proc () { - [ ] `new site` set up new project - [ ] warn/error when unknown key used in mustache. - [ ] Import/export packages. Hugo, jekyll, WordPress, etc. +- [ ] opt-in "strict_keys" mode. in this mode, key lookups may not view parent objects. ## Notes diff --git a/butts b/butts new file mode 100644 index 0000000..e69de29 diff --git a/content.odin b/content.odin index 1631d57..220c6f9 100644 --- a/content.odin +++ b/content.odin @@ -7,6 +7,7 @@ import "core:fmt" import "core:log" import "core:os" import "core:strings" +import "core:time" // Fields with underscores should never be set by the user. Page :: struct { @@ -18,12 +19,13 @@ Page :: struct { title: string, description: string, date: string, + year: string, lastmod: string, menu: string, content: string, og: Open_Graph, draft: bool, - is_starred: bool, + starred: bool, _is_index: bool `private`, } @@ -231,9 +233,18 @@ load_page :: proc( page.title = fm.title page.description = fm.description page.date = fm.date + if page.date == "" { + info, stat_err := os.stat(file_path, context.allocator) + if stat_err == nil { + page.date, _ = time.time_to_rfc3339(info.modification_time, 0, false, context.allocator) + os.file_info_delete(info, context.allocator) + log.warnf("no date in frontmatter for %s, using file modification time: %s", file_path, page.date) + } + } + page.year = get_year(page.date) page.lastmod = fm.lastmod page.draft = fm.draft - page.is_starred = fm.isStarred + page.starred = fm.isStarred page.menu = fm.menu page.layout = fm.layout if fm.layout != "" else infer_layout(section, is_index) page.og = fm.og diff --git a/defaults/layouts/page.html b/defaults/layouts/page.html index 75edc96..32006bf 100644 --- a/defaults/layouts/page.html +++ b/defaults/layouts/page.html @@ -2,7 +2,7 @@ {{$main}}
-

{{page_title}}

+

{{page.title}}

{{#date_iso}} {{/date_iso}} {{&content}}
diff --git a/defaults/layouts/section_index.html b/defaults/layouts/section_index.html index 96b6dd2..d856c11 100644 --- a/defaults/layouts/section_index.html +++ b/defaults/layouts/section_index.html @@ -1,7 +1,7 @@ {{ -

{{page_title}}

+

{{page.title}}

{{&content}} {{#posts | group_by year}}
diff --git a/mustache/suggest_test.odin b/mustache/suggest_test.odin index 3a168de..635ff3a 100644 --- a/mustache/suggest_test.odin +++ b/mustache/suggest_test.odin @@ -12,7 +12,7 @@ Inner :: struct { Outer :: struct { title: string, - page_title: string, + page.title: string, inner: Inner, numbers: [3]int, } @@ -150,8 +150,8 @@ test_validate_map_path_silent :: proc(t: ^testing.T) { @(test) test_suggest_correction_exact :: proc(t: ^testing.T) { - available := []string{"title", "page_title", "body"} - testing.expect_value(t, suggest_correction(available, "page_titel"), "page_title") + available := []string{"title", "page.title", "body"} + testing.expect_value(t, suggest_correction(available, "page_titel"), "page.title") } @(test) diff --git a/render.odin b/render.odin index 40e8d84..0acbeb8 100644 --- a/render.odin +++ b/render.odin @@ -19,35 +19,14 @@ Template_Context :: struct { og: Open_Graph, date_format: string, timezone: ^datetime.TZ_Region, - - // Page Data - page_title: string, - date: string, + page: Page, // Home data - pages: [dynamic]Page_Context, + pages: [dynamic]Page, // Section Data // TODO: Remove "posts" from the Odin code - posts: [dynamic]Page_Context, -} - -Page_Context :: struct { - permalink: string, - title: string, - starred: bool, - date: string, - year: string, -} - -build_page_context :: proc(page: Page) -> Page_Context { - return Page_Context { - permalink = page.permalink, - title = page.title, - starred = page.is_starred, - date = page.date, - year = get_year(page.date), - } + posts: [dynamic]Page, } load_template :: proc(vfs: ^VFS, virtual_path: string) -> mustache.Template { @@ -268,9 +247,8 @@ render_page_html :: proc( ) -> string { ctx := ctx ctx.title = fmt.tprintf("%s | %s", page.title, site.title) - ctx.page_title = page.title + ctx.page = page ctx.content = page.content - ctx.date = page.date ctx.og = og_for_page(site.og, page) return render_template(content_tpl, ctx, partials) } @@ -282,13 +260,12 @@ render_home_html :: proc( partials: map[string]mustache.Template, ctx: Template_Context, ) -> string { - list_pages := make([dynamic]Page_Context) - defer delete(list_pages) + list_pages := make([dynamic]Page, 0, 8, context.temp_allocator) for page in site.pages { if page._is_index { continue } - append(&list_pages, build_page_context(page)) + append(&list_pages, page) } ctx := ctx @@ -309,24 +286,25 @@ render_section :: proc( partials: map[string]mustache.Template, ctx: Template_Context, ) -> string { - posts := make([dynamic]Page_Context) - defer delete(posts) + posts := make([dynamic]Page, 0, len(site.pages) / 2, context.temp_allocator) for page in site.pages { if page.section != section || page._is_index { continue } - append(&posts, build_page_context(page)) + append(&posts, page) } ctx := ctx if has_index { ctx.content = section_index.content - ctx.page_title = section_index.title + ctx.page = section_index ctx.title = fmt.tprintf("%s | %s", section_index.title, site.title) ctx.og = og_for_page(site.og, section_index) } else { - ctx.page_title = capitalize(section) - ctx.title = fmt.tprintf("%s | %s", capitalize(section), site.title) + ctx.page = Page { + title = capitalize(section), + } + ctx.title = fmt.tprintf("%s | %s", ctx.page.title, site.title) ctx.og.title = capitalize(section) ctx.og.description = "" ctx.og.url = fmt.tprintf("%s/%s/", site.base_url, section)