// TODO // - should the path containing the contents of the tui-website reside in the // hardcode `./doc` directory or shall this become a compilation argument to // allow for custom directory locations? // usage: tui_website // : partial path to the document without the file extension and the beginning `./doc` path // ending `/` (as used from the web-world) are trimmed automatically pub fn main() !void { errdefer |err| log.err("Application Error: {any}", .{err}); var gpa: std.heap.DebugAllocator(.{}) = .init; defer if (gpa.deinit() == .leak) log.err("memory leak", .{}); const allocator = gpa.allocator(); var arg_it = try std.process.argsWithAllocator(allocator); errdefer arg_it.deinit(); // skip own executable name _ = arg_it.skip(); var app: App = .init(.{}, .{ .page = .about, .document = .init(try std.fs.cwd().readFileAlloc(allocator, "./doc/about.md", std.math.maxInt(usize))), }); defer app.model.deinit(allocator); var renderer = zterm.Renderer.Buffered.init(allocator); defer renderer.deinit(); var container = try App.Container.init(allocator, .{ .layout = .{ .padding = .horizontal(2), .direction = .vertical, .separator = .{ .enabled = true }, }, }, .{}); defer container.deinit(); var content_container: App.Container = undefined; // header with navigation buttons and content's title { var header: App.Container = try .init(allocator, .{ .size = .{ .dim = .{ .y = 1 }, .grow = .horizontal, }, .layout = .{ .separator = .{ .enabled = true }, }, }, .{}); // title { var title: Title = .{}; try header.append(try .init(allocator, .{}, title.element())); } // about navigation button { var button: NavigationButton(.about) = .init(&app.model, &app.queue); try header.append(try .init(allocator, .{ .size = .{ .dim = .{ .x = 5 + 2 }, .grow = .vertical, }, }, button.element())); } // blog navigation button { var button: NavigationButton(.blog) = .init(&app.model, &app.queue); try header.append(try .init(allocator, .{ .size = .{ .dim = .{ .x = 4 + 2 }, .grow = .vertical, }, }, button.element())); } try container.append(header); } // main actual tui_website page content { var content: Content = .init(allocator, &app.model.document); content_container = try .init(allocator, .{}, content.element()); var scrollable: App.Scrollable = .init(content_container, .enabled(.green, false)); // intermediate container for *padding* containing the scrollable `Content` var scrollable_container: App.Container = try .init(allocator, .{ .layout = .{ .padding = .horizontal(2) }, }, .{}); try scrollable_container.append(try .init(allocator, .{}, scrollable.element())); try container.append(scrollable_container); } // footer { var info_banner: InfoBanner = .{}; const footer: App.Container = try .init(allocator, .{ .size = .{ .dim = .{ .y = 1 }, .grow = .horizontal, }, }, info_banner.element()); try container.append(footer); } try app.start(); defer app.stop() catch |err| log.err("Failed to stop application: {any}", .{err}); if (arg_it.next()) |path| { // TODO check path is only pointing to allowed files? // - only *markdown* files (file extension `.md`) // - only in a specific path / directory? // -> enforce a specific structure? // -> in case an invalid path is provided the 404 error page shall be shown // -> reporte an corresponding error! (such that I can see that in the log) var blog = path[0..path.len]; blog = std.mem.trimStart(u8, blog, "./"); blog = std.mem.trimEnd(u8, blog, ".md"); blog = std.mem.trimEnd(u8, blog, "/"); app.postEvent(.{ .blog = try std.fmt.allocPrint(allocator, "./doc/{s}.md", .{blog}) }); } arg_it.deinit(); // event loop loop: while (true) { // batch events since last iteration const len = blk: { app.queue.poll(); app.queue.lock(); defer app.queue.unlock(); break :blk app.queue.len(); }; // handle events for (0..len) |_| { const event = app.queue.pop(); // pre event handling switch (event) { .key => |key| { if (key.eql(.{ .cp = 'c', .mod = .{ .ctrl = true } }) or key.eql(.{ .cp = 'q' })) app.quit(); // test if the event handling is working correctly if (key.eql(.{ .cp = zterm.input.Space })) app.postEvent(.{ .blog = allocator.dupe(u8, "./doc/test.md") catch unreachable }); }, .about => log.info(ResourceRequestFormat, .{"./doc/about.md"}), .blog => |path| log.info(ResourceRequestFormat, .{if (path) |p| p else "./doc/blog.md"}), .err => |err| log.err("Received {s} with message: {s}", .{ @errorName(err.err), err.msg }), else => {}, } container.handle(&app.model, event) catch |err| app.postEvent(.{ .err = .{ .err = err, .msg = "Container Event handling failed", }, }); // post event handling switch (event) { .quit => break :loop, else => {}, } } container.resize(&app.model, try renderer.resize()); container.reposition(&app.model, .{}); try renderer.render(@TypeOf(container), &container, App.Model, &app.model); try renderer.flush(); } } pub const panic = App.panic_handler; pub const std_options = zlog.std_options; const log = std.log.scoped(.default); const ResourceRequestFormat = "Requesting resource: `{s}`"; const std = @import("std"); const assert = std.debug.assert; const zlog = @import("zlog"); const zterm = @import("zterm"); const App = zterm.App( Model, Model.Pages, ); const contents = @import("content.zig"); const Model = @import("model.zig"); const Content = contents.Content(App); const Title = contents.Title(App); const InfoBanner = contents.InfoBanner(App); const NavigationButton = @import("navigation.zig").NavigationButton(App); test { std.testing.refAllDeclsRecursive(@This()); }