From 55861114dc7a0552ec85bdc5e4737d67de15ce00 Mon Sep 17 00:00:00 2001 From: Yves Biener Date: Sat, 1 Nov 2025 17:41:33 +0100 Subject: [PATCH] feat(content): parse document preemble Show title stored in preemble of markdown file if available. --- doc/blog.md | 6 ++++-- src/content.zig | 39 +++++++++++++++++++++------------------ src/main.zig | 8 ++++++-- src/model.zig | 42 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 73 insertions(+), 22 deletions(-) diff --git a/doc/blog.md b/doc/blog.md index 0b3cb8f..bb5652b 100644 --- a/doc/blog.md +++ b/doc/blog.md @@ -1,5 +1,7 @@ -# Blog - +--- +title: Welcome to my Blog +date: 2025-11-01 +--- This is the main entry page for my blog. I plan on writing on things I feel like worth sharing, mentioning for other developers, tinkers like me. The blog is currently a work in progress and will be updated once a first version of the tui website supports more dynamic contents. diff --git a/src/content.zig b/src/content.zig index ce678bc..bdead0a 100644 --- a/src/content.zig +++ b/src/content.zig @@ -52,8 +52,14 @@ pub fn Content(App: type) type { fn handle(ctx: *anyopaque, model: *App.Model, event: App.Event) !void { const this: *@This() = @ptrCast(@alignCast(ctx)); switch (event) { - .about => model.page = .about, - .blog => model.page = .blog, + .about => { + model.page = .about; + model.document = .init(@embedFile("about")); + }, + .blog => { + model.page = .blog; + model.document = .init(@embedFile("blog")); + }, else => {}, } this.page = model.page; @@ -63,10 +69,7 @@ pub fn Content(App: type) type { _ = ctx; assert(cells.len == @as(usize, size.x) * @as(usize, size.y)); - const text = switch (model.page) { - .about => @embedFile("about"), - .blog => @embedFile("blog"), - }; + const text = model.document.content; var index: usize = 0; for (0..size.y) |row| { @@ -89,12 +92,8 @@ pub fn Content(App: type) type { pub fn Title(App: type) type { return struct { - text: []const u8, - pub fn init() @This() { - return .{ - .text = "", - }; + return .{}; } pub fn element(this: *@This()) App.Element { @@ -106,16 +105,20 @@ pub fn Title(App: type) type { }; } - fn content(ctx: *anyopaque, _: *const App.Model, cells: []zterm.Cell, size: zterm.Point) !void { + fn content(ctx: *anyopaque, model: *const App.Model, cells: []zterm.Cell, size: zterm.Point) !void { const this: *const @This() = @ptrCast(@alignCast(ctx)); + _ = this; assert(cells.len == @as(usize, size.x) * @as(usize, size.y)); - for (0.., this.text) |idx, cp| { - cells[idx].style.fg = .green; - cells[idx].style.emphasis = &.{.bold}; - cells[idx].cp = cp; - // NOTE do not write over the contents of this `Container`'s `Size` - if (idx == cells.len - 1) break; + if (model.document.title) |text| { + for (0.., text) |idx, cp| { + cells[idx].style.fg = .green; + cells[idx].style.emphasis = &.{.bold}; + cells[idx].cp = cp; + + // NOTE do not write over the contents of this `Container`'s `Size` + if (idx == cells.len - 1) break; + } } } }; diff --git a/src/main.zig b/src/main.zig index 4400d16..02b9e61 100644 --- a/src/main.zig +++ b/src/main.zig @@ -6,7 +6,9 @@ pub fn main() !void { const allocator = gpa.allocator(); - var app: App = .init(.{}); + var app: App = .init(.{ + .document = .init(@embedFile("blog")), + }); var renderer = zterm.Renderer.Buffered.init(allocator); defer renderer.deinit(); @@ -18,6 +20,8 @@ pub fn main() !void { }, }, .{}); defer container.deinit(); + var content_container: App.Container = undefined; + defer content_container.deinit(); // header with navigation buttons and content's title { @@ -60,7 +64,7 @@ pub fn main() !void { // main actual tui_website page content { var content: Content = .init(allocator); - const content_container: App.Container = try .init(allocator, .{}, content.element()); + content_container = try .init(allocator, .{}, content.element()); var scrollable: App.Scrollable = .init(content_container, .enabled(.green)); // intermediate container for *padding* containing the scrollable `Content` diff --git a/src/model.zig b/src/model.zig index b36ca80..a23c1f6 100644 --- a/src/model.zig +++ b/src/model.zig @@ -1,6 +1,48 @@ page: Pages = .blog, +document: Document = undefined, pub const Pages = enum { about, blog, }; + +pub const Document = struct { + // preemble: + title: ?[]const u8 = null, + date: ?[]const u8 = null, + content: []const u8, + + pub fn init(content: []const u8) @This() { + if (std.mem.startsWith(u8, content, "---\n")) { + var document: @This() = .{ .content = undefined }; + // we assume the content includes a preemble + var iter = std.mem.splitSequence(u8, content, "---\n"); + assert(iter.next().?.len == 0); + if (iter.next()) |preemble| { + var lines = std.mem.splitScalar(u8, preemble, '\n'); + while (lines.next()) |line| { + var entry = std.mem.splitSequence(u8, line, ": "); + const key = entry.next().?; + const value = entry.next(); + assert(entry.next() == null); + if (std.meta.stringToEnum(Preemble, key)) |k| switch (k) { + .title => document.title = value, + .date => document.date = value, + }; + } + } + document.content = iter.next().?; + return document; + } else return .{ + .content = content, + }; + } +}; + +const Preemble = enum { + title, + date, +}; + +const std = @import("std"); +const assert = std.debug.assert;