lib/zen/src/site/catalog/model.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2
3 pub const Dispatch = enum {
4 document,
5 presentation,
6 };
7
8 pub const Entry = struct {
9 relative_path: []const u8,
10 url: []const u8,
11 title: []const u8,
12 date: []const u8 = "",
13 section: []const u8 = "",
14 type_label: []const u8 = "",
15 dispatch: Dispatch = .document,
16 draft: bool = false,
17 slop: bool = false,
18 };
19
20 pub const EntryInput = struct {
21 relative_path: []const u8,
22 url: []const u8,
23 title: []const u8,
24 date: []const u8 = "",
25 type_label: []const u8 = "",
26 dispatch: Dispatch = .document,
27 draft: bool = false,
28 slop: bool = false,
29 };
30
31 pub const Catalog = struct {
32 base: []const u8 = "",
33 pages: []const Entry = &.{},
34
35 pub fn findRoute(self: Catalog, route: []const u8) ?*const Entry {
36 for (self.pages) |*entry| {
37 if (routeMatches(self.base, entry.url, route)) return entry;
38 }
39 return null;
40 }
41
42 pub fn findSource(self: Catalog, relative_path: []const u8) ?*const Entry {
43 for (self.pages) |*entry| {
44 if (std.mem.eql(u8, entry.relative_path, relative_path)) return entry;
45 }
46 return null;
47 }
48 };
49
50 pub const Exhaustion = error{
51 SiteCatalogPageCapacityExceeded,
52 SiteCatalogTextCapacityExceeded,
53 };
54
55 pub const Error = Exhaustion || error{CapacityOverflow};
56
57 pub fn resolveDispatch(value: ?[]const u8, fallback: Dispatch) error{InvalidRenderMode}!Dispatch {
58 const render = value orelse return fallback;
59 if (std.mem.eql(u8, render, "document")) return .document;
60 if (std.mem.eql(u8, render, "slides")) return .presentation;
61 return error.InvalidRenderMode;
62 }
63
64 pub fn section(relative_path: []const u8) []const u8 {
65 const split = std.mem.indexOfScalar(u8, relative_path, '/') orelse {
66 const result = relative_path[0..0];
67 std.debug.assert(result.len == 0);
68 return result;
69 };
70 std.debug.assert(split < relative_path.len);
71 const result = relative_path[0..split];
72 std.debug.assert(result.len == split);
73 return result;
74 }
75
76 pub fn lessThan(_: void, left: Entry, right: Entry) bool {
77 std.debug.assert(left.section.len <= left.relative_path.len);
78 std.debug.assert(right.section.len <= right.relative_path.len);
79 if (left.date.len != 0 and right.date.len != 0) {
80 const order = std.mem.order(u8, left.date, right.date);
81 if (order != .eq) return order == .gt;
82 } else if (left.date.len != right.date.len) {
83 return left.date.len != 0;
84 }
85 if (left.draft != right.draft) return !left.draft;
86 const title_order = std.ascii.orderIgnoreCase(left.title, right.title);
87 if (title_order != .eq) return title_order == .lt;
88 return std.mem.lessThan(u8, left.relative_path, right.relative_path);
89 }
90
91 fn routeMatches(base: []const u8, url: []const u8, route: []const u8) bool {
92 const route_bytes = std.math.add(usize, base.len, url.len) catch return false;
93 if (route.len != route_bytes) return false;
94 return std.mem.eql(u8, route[0..base.len], base) and
95 std.mem.eql(u8, route[base.len..], url);
96 }