8 Commits

Author SHA1 Message Date
b4836b8d2b mod: bump to version 0.0.4
All checks were successful
Zig Project Action / Lint, Spell-check and test zig project (push) Successful in 55s
Release Zig Application / Release zig project (release) Successful in 1m36s
2025-11-08 12:54:20 +01:00
5ee2c6662e feat: invalid page for unavailable resources
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.
2025-11-08 12:50:25 +01:00
28afdc0ff9 mod: bump to version 0.0.3
All checks were successful
Zig Project Action / Lint, Spell-check and test zig project (push) Successful in 55s
Release Zig Application / Release zig project (release) Successful in 1m35s
2025-11-05 22:12:27 +01:00
b228c69ae5 mod: bump zterm dependency
All checks were successful
Zig Project Action / Lint, Spell-check and test zig project (push) Successful in 1m7s
2025-11-05 22:10:48 +01:00
489d958ac5 mod: bump zterm dependency; respect scrollbar in calculated size requirement for content
All checks were successful
Zig Project Action / Lint, Spell-check and test zig project (push) Successful in 1m19s
2025-11-05 18:47:59 +01:00
f197416b43 feat: accept argument for path to open
All checks were successful
Zig Project Action / Lint, Spell-check and test zig project (push) Successful in 1m14s
2025-11-02 21:34:11 +01:00
c599bc55c7 mod: bump zterm dependency; show no background for content scrollbar 2025-11-02 21:33:30 +01:00
c5d674ac5c mod: include timestamps for logging 2025-11-02 21:31:56 +01:00
5 changed files with 88 additions and 33 deletions

View File

@@ -7,7 +7,7 @@ pub fn build(b: *std.Build) void {
const zlog = b.dependency("zlog", .{ const zlog = b.dependency("zlog", .{
.target = target, .target = target,
.optimize = optimize, .optimize = optimize,
.timestamp = false, .timestamp = true,
.stderr = false, .stderr = false,
.file = "log", .file = "log",
}); });

View File

@@ -1,13 +1,13 @@
.{ .{
.name = .tui_website, .name = .tui_website,
// This is a [Semantic Version](https://semver.org/). // This is a [Semantic Version](https://semver.org/).
.version = "0.0.2", .version = "0.0.4",
.fingerprint = 0x93d98a4d9d000e9c, // Changing this has security and trust implications. .fingerprint = 0x93d98a4d9d000e9c, // Changing this has security and trust implications.
.minimum_zig_version = "0.16.0-dev.463+f624191f9", .minimum_zig_version = "0.16.0-dev.463+f624191f9",
.dependencies = .{ .dependencies = .{
.zterm = .{ .zterm = .{
.url = "git+https://gitea.yves-biener.de/yves-biener/zterm#b3dc8096d76d6e246d48f9034a65997c0047c3c6", .url = "git+https://gitea.yves-biener.de/yves-biener/zterm#ad32e46bc9fd6d1d9b7a3615979a3b80877c9300",
.hash = "zterm-0.3.0-1xmmENP4GwBw65udohsoaxc9Kp4Yo7kORt4okY5pLslr", .hash = "zterm-0.3.0-1xmmEO8PHABP4tE6BnEZllJTh6Hpza_5C9wS8s2vjwou",
}, },
.zlog = .{ .zlog = .{
.url = "git+https://gitea.yves-biener.de/yves-biener/zlog#f43034cea9a0863e618c3d0a43706ce38c8791cf", .url = "git+https://gitea.yves-biener.de/yves-biener/zlog#f43034cea9a0863e618c3d0a43706ce38c8791cf",

View File

@@ -3,10 +3,10 @@ pub fn Content(App: type) type {
allocator: Allocator, allocator: Allocator,
document: *const App.Model.Document, document: *const App.Model.Document,
pub fn init(allocator: Allocator) @This() { pub fn init(allocator: Allocator, document: *const App.Model.Document) @This() {
return .{ return .{
.allocator = allocator, .allocator = allocator,
.document = undefined, .document = document,
}; };
} }
@@ -28,7 +28,7 @@ pub fn Content(App: type) type {
var new_size: zterm.Point = .{ .x = size.x }; var new_size: zterm.Point = .{ .x = size.x };
for (0..text.len) |_| rows: { for (0..text.len) |_| rows: {
for (0..size.x) |_| { for (0..size.x - 1) |_| { // assume that there is one cell reserved for the scrollbar
if (index == text.len) break :rows; if (index == text.len) break :rows;
const cp = text[index]; const cp = text[index];
index += 1; index += 1;
@@ -57,10 +57,16 @@ pub fn Content(App: type) type {
}, },
.blog => |path| { .blog => |path| {
model.page.deinit(this.allocator); model.page.deinit(this.allocator);
model.document.deinit(this.allocator);
errdefer {
if (path) |p| this.allocator.free(p);
model.document = .invalidPage;
model.page = .{ .blog = null };
}
model.document = .init(try std.fs.cwd().readFileAlloc(if (path) |p| p else "./doc/blog.md", this.allocator, .unlimited));
model.page = .{ .blog = path }; model.page = .{ .blog = path };
model.document.deinit(this.allocator);
model.document = .init(try std.fs.cwd().readFileAlloc(if (path) |p| p else "./doc/blog.md", this.allocator, .unlimited));
this.document = &model.document; this.document = &model.document;
}, },
else => {}, else => {},

View File

@@ -1,3 +1,11 @@
// 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 { pub fn main() !void {
errdefer |err| log.err("Application Error: {any}", .{err}); errdefer |err| log.err("Application Error: {any}", .{err});
@@ -6,6 +14,12 @@ pub fn main() !void {
const allocator = gpa.allocator(); 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(.{ var app: App = .init(.{
.page = .about, .page = .about,
.document = .init(try std.fs.cwd().readFileAlloc("./doc/about.md", allocator, .unlimited)), .document = .init(try std.fs.cwd().readFileAlloc("./doc/about.md", allocator, .unlimited)),
@@ -66,10 +80,10 @@ pub fn main() !void {
} }
// main actual tui_website page content // main actual tui_website page content
{ {
var content: Content = .init(allocator); var content: Content = .init(allocator, &app.model.document);
content_container = try .init(allocator, .{}, content.element()); content_container = try .init(allocator, .{}, content.element());
var scrollable: App.Scrollable = .init(content_container, .enabled(.green)); var scrollable: App.Scrollable = .init(content_container, .enabled(.green, false));
// intermediate container for *padding* containing the scrollable `Content` // intermediate container for *padding* containing the scrollable `Content`
var scrollable_container: App.Container = try .init(allocator, .{ var scrollable_container: App.Container = try .init(allocator, .{
.layout = .{ .padding = .horizontal(2) }, .layout = .{ .padding = .horizontal(2) },
@@ -92,18 +106,44 @@ pub fn main() !void {
try app.start(); try app.start();
defer app.stop() catch |err| log.err("Failed to stop application: {any}", .{err}); 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 // event loop
while (true) { 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 // handle events
const event = app.nextEvent(); for (0..len) |_| {
const event = app.queue.pop();
// pre event handling // pre event handling
switch (event) { switch (event) {
.key => |key| { .key => |key| {
if (key.eql(.{ .cp = 'c', .mod = .{ .ctrl = true } })) app.quit(); if (key.eql(.{ .cp = 'c', .mod = .{ .ctrl = true } }) or key.eql(.{ .cp = 'q' })) app.quit();
// test if the event handling is working correctly // 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 }); 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 }), .err => |err| log.err("Received {s} with message: {s}", .{ @errorName(err.err), err.msg }),
else => {}, else => {},
} }
@@ -117,9 +157,10 @@ pub fn main() !void {
// post event handling // post event handling
switch (event) { switch (event) {
.quit => break, .quit => break :loop,
else => {}, else => {},
} }
}
container.resize(try renderer.resize()); container.resize(try renderer.resize());
container.reposition(.{}); container.reposition(.{});
@@ -133,6 +174,8 @@ pub const panic = App.panic_handler;
pub const std_options = zlog.std_options; pub const std_options = zlog.std_options;
const log = std.log.scoped(.default); const log = std.log.scoped(.default);
const ResourceRequestFormat = "Requesting resource: `{s}`";
const std = @import("std"); const std = @import("std");
const assert = std.debug.assert; const assert = std.debug.assert;
const zlog = @import("zlog"); const zlog = @import("zlog");

View File

@@ -33,6 +33,12 @@ pub const Document = struct {
date, date,
}; };
pub const invalidPage: @This() = .{
.title = "Page not found",
.content = "Requested page does not exist",
.ptr = undefined,
};
pub fn init(content: []const u8) @This() { pub fn init(content: []const u8) @This() {
if (std.mem.startsWith(u8, content, "---\n")) { if (std.mem.startsWith(u8, content, "---\n")) {
var document: @This() = .{ .ptr = content }; var document: @This() = .{ .ptr = content };