All checks were successful
Zig Project Action / Lint, Spell-check and test zig project (push) Successful in 1m46s
Accessing resources which are not available, the invalid page will be rendered instead. The original attempt for access will be logged.
198 lines
6.8 KiB
Zig
198 lines
6.8 KiB
Zig
// 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 <location>
|
|
// <location>: 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("./doc/about.md", allocator, .unlimited)),
|
|
});
|
|
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;
|
|
defer content_container.deinit();
|
|
|
|
// 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(try renderer.resize());
|
|
container.reposition(.{});
|
|
|
|
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());
|
|
}
|