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 }